PathFuncTest.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. unit PathFuncTest;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2026 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Test unit for PathFunc
  8. Runs a self-test if DEBUG is defined
  9. }
  10. interface
  11. procedure PathFuncRunTests(const AlsoTestJapaneseDBCS: Boolean);
  12. implementation
  13. uses
  14. Windows, SysUtils, PathFunc;
  15. procedure PathFuncRunTests(const AlsoTestJapaneseDBCS: Boolean);
  16. procedure Test(const Filename: String;
  17. const DrivePartFalse, DrivePartTrue, PathPartFalse, PathPartTrue: Integer);
  18. begin
  19. if PathDrivePartLengthEx(Filename, False) <> DrivePartFalse then
  20. raise Exception.CreateFmt('"%s" drive part(False) test failed', [Filename]);
  21. if PathDrivePartLengthEx(Filename, True) <> DrivePartTrue then
  22. raise Exception.CreateFmt('"%s" drive part(True) test failed', [Filename]);
  23. if PathPathPartLength(Filename, False) <> PathPartFalse then
  24. raise Exception.CreateFmt('"%s" path part(False) test failed', [Filename]);
  25. if PathPathPartLength(Filename, True) <> PathPartTrue then
  26. raise Exception.CreateFmt('"%s" path part(True) test failed', [Filename]);
  27. if PathIsRooted(Filename) <> (PathDrivePartLengthEx(Filename, True) <> 0) then
  28. raise Exception.CreateFmt('"%s" PathIsRooted test failed', [Filename]);
  29. end;
  30. procedure TestRemoveBackslash(const Filename, ExpectedResult: String);
  31. begin
  32. if RemoveBackslash(Filename) <> ExpectedResult then
  33. raise Exception.Create('RemoveBackslash test failed');
  34. end;
  35. procedure TestRemoveBackslashUnlessRoot(const Filename, ExpectedResult: String);
  36. begin
  37. if RemoveBackslashUnlessRoot(Filename) <> ExpectedResult then
  38. raise Exception.Create('RemoveBackslashUnlessRoot test failed');
  39. end;
  40. procedure TestPathChangeExt(const Filename, Extension, ExpectedResult: String);
  41. begin
  42. if PathChangeExt(Filename, Extension) <> ExpectedResult then
  43. raise Exception.Create('PathChangeExt test failed');
  44. end;
  45. procedure TestPathExtractExt(const Filename, ExpectedResult: String);
  46. begin
  47. if PathExtractExt(Filename) <> ExpectedResult then
  48. raise Exception.Create('PathExtractExt test failed');
  49. end;
  50. procedure TestPathCombine(const Dir, Filename, ExpectedResult: String);
  51. begin
  52. if PathCombine(Dir, Filename) <> ExpectedResult then
  53. raise Exception.Create('PathCombine test failed');
  54. end;
  55. procedure TestPathStartsWith(const S, AStartsWith: String; const ExpectedResult: Boolean);
  56. begin
  57. if PathStartsWith(S, AStartsWith) <> ExpectedResult then
  58. raise Exception.Create('PathStartsWith test failed');
  59. end;
  60. procedure TestPathEndsWith(const IgnoreCase: Boolean;
  61. const S, AEndsWith: String; const ExpectedResult: Boolean);
  62. begin
  63. if PathEndsWith(S, AEndsWith, IgnoreCase) <> ExpectedResult then
  64. raise Exception.Create('PathEndsWith test failed');
  65. end;
  66. procedure TestPathExpand(const S, ExpectedResult: String;
  67. const ExpectedResultFromTwoParamOverload: Boolean);
  68. begin
  69. if PathExpand(S) <> ExpectedResult then
  70. raise Exception.Create('PathExpand test failed');
  71. var PathExpandResult: String;
  72. if PathExpand(S, PathExpandResult) <> ExpectedResultFromTwoParamOverload then
  73. raise Exception.Create('PathExpand test failed');
  74. if ExpectedResultFromTwoParamOverload and (PathExpandResult <> ExpectedResult) then
  75. raise Exception.Create('PathExpand test failed');
  76. end;
  77. procedure TestPathExpandAndNormalizeSlashes(const S, ExpectedResult: String);
  78. begin
  79. { PathExpand's work is done by Windows' GetFullPathName, while
  80. PathNormalizeSlashes uses our own code. They should produce the same
  81. result when the path is fully qualified and has no '.' or '..'
  82. components. }
  83. var PathExpandResult: String;
  84. if (PathExpand(S) <> ExpectedResult) or
  85. not PathExpand(S, PathExpandResult) or
  86. (PathExpandResult <> ExpectedResult) then
  87. raise Exception.Create('PathExpand test failed');
  88. if PathNormalizeSlashes(S) <> ExpectedResult then
  89. raise Exception.Create('PathNormalizeSlashes test failed');
  90. end;
  91. function CompareResultSign(const Value: Integer): Integer;
  92. begin
  93. if Value < 0 then
  94. Result := -1
  95. else if Value > 0 then
  96. Result := 1
  97. else
  98. Result := 0;
  99. end;
  100. procedure TestPathStrCompare(const S1, S2: String;
  101. const IgnoreCase: Boolean; const ExpectedSign: Integer;
  102. const UseNullTerminatedLengths: Boolean = False);
  103. begin
  104. var S1Length, S2Length: Integer;
  105. if UseNullTerminatedLengths then begin
  106. S1Length := -1;
  107. S2Length := -1;
  108. end else begin
  109. S1Length := Length(S1);
  110. S2Length := Length(S2);
  111. end;
  112. if CompareResultSign(PathStrCompare(PChar(S1), S1Length, PChar(S2), S2Length, IgnoreCase)) <> ExpectedSign then
  113. raise Exception.Create('PathStrCompare test failed');
  114. end;
  115. procedure TestPathStrFind(const Source, Value: String;
  116. const IgnoreCase: Boolean; const ExpectedIndex: Integer;
  117. const UseNullTerminatedLengths: Boolean = False);
  118. begin
  119. var SourceLength, ValueLength: Integer;
  120. if UseNullTerminatedLengths then begin
  121. SourceLength := -1;
  122. ValueLength := -1;
  123. end else begin
  124. SourceLength := Length(Source);
  125. ValueLength := Length(Value);
  126. end;
  127. if PathStrFind(PChar(Source), SourceLength, PChar(Value), ValueLength, IgnoreCase) <> ExpectedIndex then
  128. raise Exception.Create('PathStrFind test failed');
  129. end;
  130. const
  131. DBChar = #131'\'; { a double-byte character whose 2nd byte happens to be a backslash }
  132. begin
  133. {$IFDEF UNICODE}
  134. if AlsoTestJapaneseDBCS then
  135. raise Exception.Create('DBCS tests not supported in Unicode build');
  136. {$ENDIF}
  137. if AlsoTestJapaneseDBCS and (GetACP <> 932) then
  138. raise Exception.Create('Must be running in Japanese code page to run these tests');
  139. { * = Bogus path case. What the "correct" result should be is debatable. }
  140. { ** = Possible access to NTFS alternate data stream. The characters before
  141. and after the colon must be kept together as a single component. }
  142. Test('', 0, 0, 0, 0);
  143. Test('\', 0, 1, 1, 1);
  144. Test('\a', 0, 1, 1, 1);
  145. Test('\a\', 0, 1, 2, 3);
  146. Test('\a\b', 0, 1, 2, 3);
  147. Test('a', 0, 0, 0, 0);
  148. Test('a\', 0, 0, 1, 2);
  149. Test('a\\', 0, 0, 1, 3);
  150. Test('a\\\', 0, 0, 1, 4);
  151. Test('a\b', 0, 0, 1, 2);
  152. Test('a\b:c', 0, 0, 1, 2); {**}
  153. Test('1:', 2, 2, 2, 2); {*}
  154. Test('\:', 0, 1, 1, 1); {*}
  155. { Yes, the following is a valid path -- it specifies a stream named 'stream'
  156. on the root directory of the current drive. (Yes, directories can have
  157. named streams.) }
  158. Test('\:stream', 0, 1, 1, 1); {**}
  159. Test('c:', 2, 2, 2, 2);
  160. Test('c:a', 2, 2, 2, 2);
  161. Test('c:\', 2, 3, 3, 3);
  162. Test('c:\\', 2, 3, 3, 4);
  163. Test('c:\\\', 2, 3, 3, 5);
  164. Test('c:\a', 2, 3, 3, 3);
  165. Test('c:\a\', 2, 3, 4, 5);
  166. Test('c:\a\\', 2, 3, 4, 6);
  167. Test('c:\a\\\', 2, 3, 4, 7);
  168. Test('c:\a\b', 2, 3, 4, 5);
  169. Test('c:\a\b:c', 2, 3, 4, 5); {**}
  170. Test('\\', 2, 2, 2, 2); {*}
  171. { Odd cases follow: The extra slashes are considered to be in the drive part
  172. since PathDrivePartLength keeps slurping slashes looking for a share name
  173. that doesn't exist. }
  174. Test('\\\', 3, 3, 3, 3); {*}
  175. Test('\\\\', 4, 4, 4, 4); {*}
  176. Test('\\\\\', 5, 5, 5, 5); {*}
  177. Test('\\a', 3, 3, 3, 3); {*}
  178. Test('\\a\', 4, 4, 4, 4); {*}
  179. Test('\\a\b', 5, 5, 5, 5);
  180. Test('\\a\b\', 5, 5, 5, 6);
  181. Test('\\a\b\c', 5, 5, 5, 6);
  182. Test('\\a\b\c\', 5, 5, 7, 8);
  183. Test('\\a\b\c\d', 5, 5, 7, 8);
  184. Test('\\a\b\c\d:e', 5, 5, 7, 8); {**}
  185. Test('\\a\\\b', 7, 7, 7, 7);
  186. Test('\\a\\\b\\\', 7, 7, 7, 10);
  187. Test('\\a\\\b\\\c', 7, 7, 7, 10);
  188. Test('\\a\\\b\\\c\\\', 7, 7, 11, 14);
  189. Test('\\?\C:', 6, 6, 6, 6);
  190. Test('\\?\C:\', 6, 7, 7, 7);
  191. Test('\\?\C:\a', 6, 7, 7, 7);
  192. Test('\\?\C:\a\b', 6, 7, 8, 9);
  193. Test('\\.\C:', 6, 6, 6, 6);
  194. Test('\\.\C:\a', 6, 7, 7, 7);
  195. Test('\\?\UNC\server\share', 20, 20, 20, 20);
  196. Test('\\?\UNC\server\share\', 20, 20, 20, 21);
  197. Test('\\?\UNC\server\share\dir', 20, 20, 20, 21);
  198. Test('\\.\UNC\server\share', 20, 20, 20, 20);
  199. if AlsoTestJapaneseDBCS then begin
  200. Test('\\'+DBChar+DBChar+'\b', 8, 8, 8, 8);
  201. Test('\\'+DBChar+DBChar+'\b\c', 8, 8, 8, 9);
  202. Test('\\'+DBChar+DBChar+'\b\c\', 8, 8, 10, 11);
  203. Test('c:\'+DBChar+'\b', 2, 3, 5, 6);
  204. Test(DBChar+':', 3, 3, 3, 3); {*} { double-byte drive letter? bogus, but be like Windows... }
  205. end;
  206. TestRemoveBackslash('', '');
  207. TestRemoveBackslash('\', '');
  208. TestRemoveBackslash('\\', '');
  209. TestRemoveBackslash('\\\', '');
  210. TestRemoveBackslash('c:', 'c:');
  211. TestRemoveBackslash('c:\', 'c:');
  212. TestRemoveBackslash('c:\\', 'c:');
  213. TestRemoveBackslash('c:\\\', 'c:');
  214. if AlsoTestJapaneseDBCS then begin
  215. TestRemoveBackslash(DBChar, DBChar);
  216. TestRemoveBackslash(DBChar+'\', DBChar);
  217. TestRemoveBackslash(DBChar+'\\', DBChar);
  218. end;
  219. TestRemoveBackslashUnlessRoot('', '');
  220. TestRemoveBackslashUnlessRoot('\', '\');
  221. TestRemoveBackslashUnlessRoot('\\', '\\'); {*}
  222. TestRemoveBackslashUnlessRoot('\\\', '\\\'); {*}
  223. TestRemoveBackslashUnlessRoot('\\\\', '\\\\'); {*}
  224. TestRemoveBackslashUnlessRoot('a', 'a');
  225. TestRemoveBackslashUnlessRoot('a\', 'a');
  226. TestRemoveBackslashUnlessRoot('a\\', 'a');
  227. TestRemoveBackslashUnlessRoot('c:', 'c:');
  228. TestRemoveBackslashUnlessRoot('c:\', 'c:\');
  229. TestRemoveBackslashUnlessRoot('c:\a', 'c:\a');
  230. TestRemoveBackslashUnlessRoot('c:\a\', 'c:\a');
  231. TestRemoveBackslashUnlessRoot('c:\a\\', 'c:\a');
  232. TestRemoveBackslashUnlessRoot('\\a\b', '\\a\b');
  233. TestRemoveBackslashUnlessRoot('\\a\b\', '\\a\b');
  234. TestRemoveBackslashUnlessRoot('\\a\b\\', '\\a\b');
  235. TestPathChangeExt('c:', '.txt', 'c:.txt'); {*} { weird, but same as Delphi's ChangeFileExt }
  236. TestPathChangeExt('c:\', '.txt', 'c:\.txt'); {*}
  237. TestPathChangeExt('c:\a', '.txt', 'c:\a.txt');
  238. TestPathChangeExt('c:\a.', '.txt', 'c:\a.txt');
  239. TestPathChangeExt('c:\a.tar', '.txt', 'c:\a.txt');
  240. TestPathChangeExt('c:\a.tar.gz', '.txt', 'c:\a.tar.txt');
  241. TestPathChangeExt('c:\x.y\a', '.txt', 'c:\x.y\a.txt');
  242. TestPathChangeExt('\\x.y\a', '.txt', '\\x.y\a.txt'); {*} { ditto above }
  243. TestPathChangeExt('\\x.y\a\', '.txt', '\\x.y\a\.txt'); {*}
  244. TestPathExtractExt('c:', '');
  245. TestPathExtractExt('c:\', '');
  246. TestPathExtractExt('c:\a', '');
  247. TestPathExtractExt('c:\a.', '.');
  248. TestPathExtractExt('c:\a.txt', '.txt');
  249. TestPathExtractExt('c:\a.txt.gz', '.gz');
  250. TestPathExtractExt('c:\x.y\a', '');
  251. TestPathExtractExt('\\x.y\a', '');
  252. TestPathExtractExt('\\x.y\a.b', '');
  253. TestPathExtractExt('\\x.y\a.b\c', '');
  254. TestPathExtractExt('\\x.y\a.b\c.txt', '.txt');
  255. TestPathCombine('', 'x', 'x');
  256. TestPathCombine('a', 'x', 'a\x');
  257. TestPathCombine('a\', 'x', 'a\x');
  258. TestPathCombine('a\\', 'x', 'a\\x');
  259. TestPathCombine('c:', 'x', 'c:x');
  260. TestPathCombine('c:\', 'x', 'c:\x');
  261. TestPathCombine('c:\\', 'x', 'c:\\x');
  262. TestPathCombine('c:\a', 'x', 'c:\a\x');
  263. TestPathCombine('\', 'x', '\x');
  264. if AlsoTestJapaneseDBCS then begin
  265. TestPathCombine(DBChar+':', 'x', DBChar+':x'); {*} { double-byte drive letter? bogus, but be like Windows... }
  266. TestPathCombine('c:\'+DBChar, 'x', 'c:\'+DBChar+'\x');
  267. end;
  268. TestPathCombine('c:\', '', '');
  269. TestPathCombine('c:\', 'e:x', 'e:x');
  270. TestPathCombine('c:\', 'e:\x', 'e:\x');
  271. TestPathCombine('c:\', '\x', '\x');
  272. TestPathCombine('c:\', '\\a\b\c', '\\a\b\c');
  273. TestPathCombine('c:\', 'ee:x', 'c:\ee:x'); {**}
  274. TestPathStartsWith('', '', True);
  275. TestPathStartsWith('TestingAbc', '', True);
  276. TestPathStartsWith('C:', 'c:\', False);
  277. TestPathStartsWith('C:\', 'c:\', True);
  278. TestPathStartsWith('C:\test', 'c:\', True);
  279. if AlsoTestJapaneseDBCS then begin
  280. { Test PathStartsWith's PathCharIsTrailByte call; it shouldn't chop a
  281. double-byte character in half }
  282. TestPathStartsWith('C:'+DBChar, 'c:\', False);
  283. TestPathStartsWith('C:'+DBChar, 'c:'+DBChar[1], False);
  284. end;
  285. TestPathEndsWith(False, '', '', True);
  286. TestPathEndsWith(True, '', '', True);
  287. TestPathEndsWith(True, 'TestingAbc', '', True);
  288. TestPathEndsWith(True, 'TestingAbc', 'gabc', True);
  289. TestPathEndsWith(False, 'TestingAbc', 'gabc', False);
  290. TestPathEndsWith(True, 'TestingAbc', 'zabc', False);
  291. TestPathEndsWith(True, 'TestingAbc', 'testingABC', True);
  292. TestPathEndsWith(True, 'TestingAbc', 'xTestingAbc', False);
  293. TestPathExpand('', '', False);
  294. TestPathExpand(' ', '', False);
  295. TestPathExpand(' ', '', False);
  296. { This odd behavior comes from GetFullPathName. You'd think they would fail
  297. like in the above cases. Only testing to see if the behavior changes. }
  298. TestPathExpand('...', AddBackslash(GetCurrentDir), True);
  299. TestPathExpand('.. ', AddBackslash(GetCurrentDir), True);
  300. TestPathExpandAndNormalizeSlashes('C:\abc\def', 'C:\abc\def');
  301. TestPathExpandAndNormalizeSlashes('C:\abc\def\', 'C:\abc\def\');
  302. TestPathExpandAndNormalizeSlashes('C:\abc\def\\', 'C:\abc\def\');
  303. TestPathExpandAndNormalizeSlashes('C:/abc\def', 'C:\abc\def');
  304. TestPathExpandAndNormalizeSlashes('C:\\\abc////def', 'C:\abc\def');
  305. { Windows' GetFullPathName doesn't collapse 3+ leading slashes down to 2;
  306. instead, it collapses 4+ leading slashes down to 3. (The resulting path
  307. doesn't actually work with that extra 3rd slash.) }
  308. TestPathExpandAndNormalizeSlashes('\\?\C:\Windows', '\\?\C:\Windows');
  309. TestPathExpandAndNormalizeSlashes('\\\?\C:\Windows', '\\\?\C:\Windows');
  310. TestPathExpandAndNormalizeSlashes('\\\\?\C:\Windows', '\\\?\C:\Windows');
  311. TestPathExpandAndNormalizeSlashes('\\\\\?\C:\Windows', '\\\?\C:\Windows');
  312. TestPathExpandAndNormalizeSlashes('\\?\\C:\\Windows', '\\?\C:\Windows');
  313. TestPathExpandAndNormalizeSlashes('\\\?\\C:\\Windows', '\\\?\C:\Windows');
  314. TestPathStrCompare('Test', 'test', True, 0);
  315. TestPathStrCompare('Test', 'test', False, -1);
  316. TestPathStrCompare('Test', 'Te', False, 1);
  317. TestPathStrCompare('Test', 'Tex', False, -1);
  318. TestPathStrCompare('Hello'+#0+'World', 'Hello', False, 0, True);
  319. TestPathStrFind('abcABC', 'ABC', True, 0);
  320. TestPathStrFind('abcABC', 'ABC', False, 3);
  321. TestPathStrFind('abcABC', 'AbC', False, -1);
  322. TestPathStrFind('abcABC', 'AbC', True, 0);
  323. TestPathStrFind('abcABC', 'xyz', True, -1);
  324. TestPathStrFind('abc'+#0+'ABC', 'ABC', False, -1, True);
  325. TestPathStrFind('abc'+#0+'ABC', 'ABC', False, 4);
  326. end;
  327. {$IFDEF DEBUG}
  328. initialization
  329. PathFuncRunTests(False);
  330. {$ENDIF}
  331. end.