tcrecompile.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. unit tcrecompile;
  2. {$mode ObjFPC}{$H+}
  3. interface
  4. uses
  5. Classes, SysUtils, fpcunit, testregistry, tstppuutils;
  6. type
  7. { TTestRecompile }
  8. TTestRecompile = class(TTestCase)
  9. private
  10. FCompiled: TStringList;
  11. FMainSrc: string;
  12. FOutDir: string;
  13. FPP: string;
  14. FStep: string;
  15. FUnitPath: string;
  16. protected
  17. procedure SetUp; override;
  18. procedure TearDown; override;
  19. procedure CleanOutputDir; overload;
  20. procedure CleanOutputDir(Dir: string); overload;
  21. procedure Compile;
  22. procedure CheckCompiled(const Expected: TStringArray);
  23. procedure MakeDateDiffer(const File1, File2: string);
  24. property PP: string read FPP write FPP;
  25. property UnitPath: string read FUnitPath write FUnitPath;
  26. property OutDir: string read FOutDir write FOutDir;
  27. property MainSrc: string read FMainSrc write FMainSrc;
  28. property Compiled: TStringList read FCompiled write FCompiled;
  29. property Step: string read FStep write FStep;
  30. public
  31. constructor Create; override;
  32. procedure GetCompiler;
  33. procedure CheckCompiler;
  34. published
  35. procedure TestTwoUnits; // 2 units
  36. procedure TestChangeLeaf1; // prog+2 units, change leaf
  37. procedure TestChangeInner1; // prog+2 units, change inner unit, keep leaf
  38. procedure TestChangeInlineBodyBug; // Bug: prog+1 unit plus a package of 2 units, change of inline body should change crc, but does not
  39. // inline modifier in implementation (not in interface)
  40. procedure TestImplInline1; // 2 units, cycle, impl inline
  41. procedure TestImplInline2; // program + 2 units cycle, impl inline
  42. procedure TestImplInline_Bug41291; // program plus 3 cycles
  43. procedure TestImplInline3; // program + 2 units cycle, impl inline, implementation changed
  44. end;
  45. implementation
  46. { TTestRecompile }
  47. procedure TTestRecompile.SetUp;
  48. begin
  49. inherited SetUp;
  50. UnitPath:='';
  51. OutDir:='';
  52. MainSrc:='';
  53. Compiled:=TStringList.Create;
  54. end;
  55. procedure TTestRecompile.TearDown;
  56. begin
  57. FreeAndNil(FCompiled);
  58. inherited TearDown;
  59. end;
  60. procedure TTestRecompile.CleanOutputDir;
  61. begin
  62. CleanOutputDir(OutDir);
  63. end;
  64. procedure TTestRecompile.CleanOutputDir(Dir: string);
  65. var
  66. Info: TRawByteSearchRec;
  67. Filename: String;
  68. r: LongInt;
  69. begin
  70. if Dir='' then
  71. Fail('TTestRecompile.CleanOutputDir: missing Dir');
  72. if Dir[length(Dir)]=PathDelim then
  73. Delete(Dir,length(Dir),1);
  74. if not DirectoryExists(Dir) then
  75. if not CreateDir(Dir) then
  76. Fail('unable to create output directory "'+Dir+'"');
  77. writeln('CleanOutputDir ',Dir);
  78. r:=FindFirst(Dir+PathDelim+AllFilesMask,faAnyFile,Info);
  79. try
  80. if r<>0 then exit;
  81. repeat
  82. case Info.Name of
  83. '','.','..': continue;
  84. end;
  85. if faDirectory and Info.Attr>0 then
  86. continue; // keep directories
  87. if Info.Name[1]='.' then
  88. continue; // keep hidden files
  89. case lowercase(ExtractFileExt(Info.Name)) of
  90. '.txt': continue; // keep txt files
  91. end;
  92. Filename:=Dir+PathDelim+Info.Name;
  93. if not DeleteFile(Filename) then
  94. Fail('unable to delete "'+Filename+'"');
  95. until FindNext(Info)<>0;
  96. finally
  97. FindClose(Info);
  98. end;
  99. end;
  100. procedure TTestRecompile.Compile;
  101. var
  102. Params, Lines: TStringList;
  103. i: Integer;
  104. Line, Filename: String;
  105. begin
  106. Compiled.Clear;
  107. if UnitPath='' then
  108. Fail('missing UnitPath, Step='+Step);
  109. if OutDir='' then
  110. Fail('missing OutDir, Step='+Step);
  111. if not DirectoryExists(OutDir) then
  112. Fail('OutDir not found "'+OutDir+'", Step='+Step);
  113. if MainSrc='' then
  114. Fail('missing MainSrc, Step='+Step);
  115. if not FileExists(MainSrc) then
  116. Fail('main src file not found "'+MainSrc+'", Step='+Step);
  117. Lines:=nil;
  118. Params:=TStringList.Create;
  119. try
  120. Params.Add('-Fu'+UnitPath);
  121. Params.Add('-FE'+OutDir);
  122. Params.Add(MainSrc);
  123. if not RunTool(PP,Params,'',false,true,Lines) then
  124. Fail('compile failed, Step='+Step);
  125. for i:=0 to Lines.Count-1 do
  126. begin
  127. Line:=Lines[i];
  128. if LeftStr(Line,length('Compiling '))='Compiling ' then
  129. begin
  130. Filename:=copy(Line,length('Compiling ')+1,length(Line));
  131. writeln('Compiling ',Filename);
  132. Filename:=ExtractFileName(Filename);
  133. if Compiled.IndexOf(Filename)<0 then
  134. Compiled.Add(Filename);
  135. end;
  136. end;
  137. finally
  138. Lines.Free;
  139. Params.Free;
  140. end;
  141. end;
  142. procedure TTestRecompile.CheckCompiled(const Expected: TStringArray);
  143. var
  144. i, j: Integer;
  145. begin
  146. for i:=0 to length(Expected)-1 do
  147. if (Compiled=nil) or (Compiled.IndexOf(Expected[i])<0) then
  148. Fail('missing compiling "'+Expected[i]+'", Step='+Step);
  149. for i:=0 to Compiled.Count-1 do
  150. begin
  151. j:=length(Expected)-1;
  152. while (j>=0) and (Expected[j]<>Compiled[i]) do dec(j);
  153. if j<0 then
  154. Fail('unexpected compiling "'+Compiled[i]+'", Step='+Step);
  155. end;
  156. end;
  157. procedure TTestRecompile.MakeDateDiffer(const File1, File2: string);
  158. var
  159. Age1, Age2: Int64;
  160. begin
  161. Age1:=FileAge(File1);
  162. if Age1<0 then
  163. Fail('file not found "'+File1+'"');
  164. Age2:=FileAge(File2);
  165. if Age2<0 then
  166. Fail('file not found "'+File2+'"');
  167. if Age1<>Age2 then exit;
  168. FileSetDate(File2,Age2-2);
  169. end;
  170. constructor TTestRecompile.Create;
  171. begin
  172. inherited Create;
  173. GetCompiler;
  174. end;
  175. procedure TTestRecompile.GetCompiler;
  176. begin
  177. PP:=GetEnvironmentVariable(String('PP'));
  178. if PP>'' then
  179. begin
  180. CheckCompiler;
  181. exit;
  182. end;
  183. raise Exception.Create('I need environment var "PP"');
  184. end;
  185. procedure TTestRecompile.CheckCompiler;
  186. procedure E(Msg: string);
  187. begin
  188. writeln('TTestRecompile.CheckCompiler: '+Msg);
  189. raise Exception.Create('TTestRecompile.CheckCompiler: '+Msg);
  190. end;
  191. begin
  192. if PP='' then
  193. E('missing compiler');
  194. if not FileIsExecutable(PP) then
  195. E('compiler not executable: "'+PP+'"');
  196. end;
  197. procedure TTestRecompile.TestTwoUnits;
  198. begin
  199. UnitPath:='twounits';
  200. OutDir:='twounits'+PathDelim+'ppus';
  201. MainSrc:='twounits'+PathDelim+'tppu_twounits_ant.pas';
  202. Step:='First compile';
  203. CleanOutputDir;
  204. Compile;
  205. CheckCompiled(['tppu_twounits_ant.pas','tppu_twounits_bird.pas']);
  206. Step:='Second compile';
  207. Compile;
  208. // the bird ppu does not depend on ant, so it is kept
  209. CheckCompiled(['tppu_twounits_ant.pas']);
  210. end;
  211. procedure TTestRecompile.TestChangeLeaf1;
  212. var
  213. Dir: String;
  214. begin
  215. Dir:='changeleaf1';
  216. UnitPath:=Dir+';'+Dir+PathDelim+'src1';
  217. OutDir:=Dir+PathDelim+'ppus';
  218. MainSrc:=Dir+PathDelim+'changeleaf1_prg.pas';
  219. MakeDateDiffer(
  220. Dir+PathDelim+'src1'+PathDelim+'changeleaf1_bird.pas',
  221. Dir+PathDelim+'src2'+PathDelim+'changeleaf1_bird.pas');
  222. Step:='First compile';
  223. CleanOutputDir;
  224. Compile;
  225. CheckCompiled(['changeleaf1_prg.pas','changeleaf1_ant.pas','changeleaf1_bird.pas']);
  226. Step:='Second compile';
  227. UnitPath:=Dir+';'+Dir+PathDelim+'src2';
  228. Compile;
  229. // the main src is always compiled, bird changed, so ant must be recompiled as well
  230. CheckCompiled(['changeleaf1_prg.pas','changeleaf1_ant.pas','changeleaf1_bird.pas']);
  231. end;
  232. procedure TTestRecompile.TestChangeInner1;
  233. var
  234. Dir: String;
  235. begin
  236. Dir:='changeinner1';
  237. UnitPath:=Dir+';'+Dir+PathDelim+'src1';
  238. OutDir:=Dir+PathDelim+'ppus';
  239. MainSrc:=Dir+PathDelim+'changeinner1_prg.pas';
  240. MakeDateDiffer(
  241. Dir+PathDelim+'src1'+PathDelim+'changeinner1_ant.pas',
  242. Dir+PathDelim+'src2'+PathDelim+'changeinner1_ant.pas');
  243. Step:='First compile';
  244. CleanOutputDir;
  245. Compile;
  246. CheckCompiled(['changeinner1_prg.pas','changeinner1_ant.pas','changeinner1_bird.pas']);
  247. Step:='Second compile';
  248. UnitPath:=Dir+';'+Dir+PathDelim+'src2';
  249. Compile;
  250. // the main src is always compiled, ant changed, bird is kept
  251. CheckCompiled(['changeinner1_prg.pas','changeinner1_ant.pas']);
  252. end;
  253. procedure TTestRecompile.TestChangeInlineBodyBug;
  254. var
  255. ProgDir, PkgDir, PkgOutDir: String;
  256. begin
  257. // unit testcib_elk uses an inline function of unit testcib_bird
  258. // elk belongs to the program, bird to the package, so they are compiled separately
  259. // when the inline body of bird changes, the elk.ppu must be rebuilt too
  260. ProgDir:='changeinlinebody'+PathDelim;
  261. PkgDir:=ProgDir+'pkg';
  262. PkgOutDir:=PkgDir+PathDelim+'lib';
  263. MakeDateDiffer(
  264. ProgDir+'original'+PathDelim+'testcib_bird.pas',
  265. ProgDir+'changed'+PathDelim+'testcib_bird.pas');
  266. // compile package containing testcib_ant.pas and testcib_bird.pas
  267. Step:='Compile original package';
  268. UnitPath:=PkgDir+';'+ProgDir+'original';
  269. OutDir:=PkgOutDir;
  270. MainSrc:=PkgDir+PathDelim+'testcib_ant.pas';
  271. CleanOutputDir;
  272. Compile;
  273. CheckCompiled(['testcib_ant.pas','testcib_bird.pas']);
  274. // compile program
  275. Step:='Compile program with original package ppus';
  276. UnitPath:=ProgDir+';'+PkgOutDir;
  277. OutDir:=ProgDir+'lib';
  278. MainSrc:=ProgDir+'testcib_prog.pas';
  279. CleanOutputDir;
  280. Compile;
  281. CheckCompiled(['testcib_prog.pas','testcib_elk.pas']);
  282. // recompile package with changed testcib_bird.pas
  283. Step:='Compile changed package';
  284. UnitPath:=PkgDir+';'+ProgDir+'changed';
  285. OutDir:=PkgOutDir;
  286. MainSrc:=PkgDir+PathDelim+'testcib_ant.pas';
  287. Compile;
  288. CheckCompiled(['testcib_ant.pas','testcib_bird.pas']);
  289. // recompile program
  290. Step:='Compile program with changed package ppus';
  291. UnitPath:=ProgDir+';'+PkgOutDir;
  292. OutDir:=ProgDir+'lib';
  293. MainSrc:=ProgDir+'testcib_prog.pas';
  294. Compile;
  295. // fpc should compile elk:
  296. //CheckCompiled(['testcib_prog.pas','testcib_elk.pas']);
  297. // But it does not:
  298. CheckCompiled(['testcib_prog.pas']);
  299. end;
  300. procedure TTestRecompile.TestImplInline1;
  301. // unit ant uses bird
  302. // unit bird impl uses ant and has a function with inline modifier in implementation
  303. begin
  304. UnitPath:='implinline1';
  305. OutDir:='implinline1'+PathDelim+'ppus';
  306. MainSrc:='implinline1'+PathDelim+'implinline1_ant.pas';
  307. Step:='First compile';
  308. CleanOutputDir;
  309. Compile;
  310. CheckCompiled(['implinline1_ant.pas','implinline1_bird.pas']);
  311. Step:='Second compile';
  312. Compile;
  313. // the main src is always compiled, and since bird ppu depends on ant, it is always compiled as well
  314. CheckCompiled(['implinline1_ant.pas','implinline1_bird.pas']);
  315. end;
  316. procedure TTestRecompile.TestImplInline2;
  317. // prg uses ant
  318. // unit ant uses bird
  319. // unit bird impl uses ant and has a function with inline modifier in implementation
  320. begin
  321. UnitPath:='implinline2';
  322. OutDir:='implinline2'+PathDelim+'ppus';
  323. MainSrc:='implinline2'+PathDelim+'implinline2_prg.pas';
  324. Step:='First compile';
  325. CleanOutputDir;
  326. Compile;
  327. CheckCompiled(['implinline2_prg.pas','implinline2_ant.pas','implinline2_bird.pas']);
  328. Step:='Second compile';
  329. Compile;
  330. // the main src is always compiled, the two ppus of ant and bird are kept
  331. CheckCompiled(['implinline2_prg.pas']);
  332. end;
  333. procedure TTestRecompile.TestImplInline_Bug41291;
  334. begin
  335. UnitPath:='bug41291';
  336. OutDir:='bug41291'+PathDelim+'ppus';
  337. MainSrc:='bug41291'+PathDelim+'bug41291_app.pas';
  338. Step:='First compile';
  339. CleanOutputDir;
  340. Compile;
  341. CheckCompiled(['bug41291_app.pas','bug41291_mclasses.pas','bug41291_mseapplication.pas',
  342. 'bug41291_mseclasses.pas','bug41291_mseeditglob.pas','bug41291_mseifiglob.pas']);
  343. Step:='Second compile';
  344. Compile;
  345. // the main src is always compiled, the other ppus are kept
  346. CheckCompiled(['bug41291_app.pas']);
  347. end;
  348. procedure TTestRecompile.TestImplInline3;
  349. var
  350. Dir: String;
  351. begin
  352. Dir:='implinline3';
  353. UnitPath:=Dir+';'+Dir+PathDelim+'src1';
  354. OutDir:=Dir+PathDelim+'ppus';
  355. MainSrc:=Dir+PathDelim+'implinline3_prg.pas';
  356. MakeDateDiffer(
  357. Dir+PathDelim+'src1'+PathDelim+'implinline3_ant.pas',
  358. Dir+PathDelim+'src2'+PathDelim+'implinline3_ant.pas');
  359. MakeDateDiffer(
  360. Dir+PathDelim+'src1'+PathDelim+'implinline3_bird.pas',
  361. Dir+PathDelim+'src2'+PathDelim+'implinline3_bird.pas');
  362. Step:='First compile';
  363. CleanOutputDir;
  364. Compile;
  365. CheckCompiled(['implinline3_prg.pas','implinline3_ant.pas','implinline3_bird.pas']);
  366. Step:='Second compile';
  367. UnitPath:=Dir+';'+Dir+PathDelim+'src2';
  368. Compile;
  369. // the main src is always compiled, and the ant impl changed, so bird is also compiled
  370. CheckCompiled(['implinline3_prg.pas','implinline3_ant.pas','implinline3_bird.pas']);
  371. end;
  372. initialization
  373. RegisterTests([TTestRecompile]);
  374. end.