DifferTest.php 29KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of sebastian/diff.
  4. *
  5. * (c) Sebastian Bergmann <sebastian@phpunit.de>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace SebastianBergmann\Diff;
  11. use PHPUnit\Framework\TestCase;
  12. use SebastianBergmann\Diff\Output\AbstractChunkOutputBuilder;
  13. use SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder;
  14. use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
  15. /**
  16. * @covers SebastianBergmann\Diff\Differ
  17. * @covers SebastianBergmann\Diff\Output\AbstractChunkOutputBuilder
  18. * @covers SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder
  19. * @covers SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder
  20. *
  21. * @uses SebastianBergmann\Diff\MemoryEfficientLongestCommonSubsequenceCalculator
  22. * @uses SebastianBergmann\Diff\TimeEfficientLongestCommonSubsequenceCalculator
  23. * @uses SebastianBergmann\Diff\Chunk
  24. * @uses SebastianBergmann\Diff\Diff
  25. * @uses SebastianBergmann\Diff\Line
  26. * @uses SebastianBergmann\Diff\Parser
  27. */
  28. final class DifferTest extends TestCase
  29. {
  30. const WARNING = 3;
  31. const REMOVED = 2;
  32. const ADDED = 1;
  33. const OLD = 0;
  34. /**
  35. * @var Differ
  36. */
  37. private $differ;
  38. protected function setUp()
  39. {
  40. $this->differ = new Differ;
  41. }
  42. /**
  43. * @param array $expected
  44. * @param string|array $from
  45. * @param string|array $to
  46. * @dataProvider arrayProvider
  47. */
  48. public function testArrayRepresentationOfDiffCanBeRenderedUsingTimeEfficientLcsImplementation(array $expected, $from, $to)
  49. {
  50. $this->assertSame($expected, $this->differ->diffToArray($from, $to, new TimeEfficientLongestCommonSubsequenceCalculator));
  51. }
  52. /**
  53. * @param string $expected
  54. * @param string $from
  55. * @param string $to
  56. * @dataProvider textProvider
  57. */
  58. public function testTextRepresentationOfDiffCanBeRenderedUsingTimeEfficientLcsImplementation(string $expected, string $from, string $to)
  59. {
  60. $this->assertSame($expected, $this->differ->diff($from, $to, new TimeEfficientLongestCommonSubsequenceCalculator));
  61. }
  62. /**
  63. * @param array $expected
  64. * @param string|array $from
  65. * @param string|array $to
  66. * @dataProvider arrayProvider
  67. */
  68. public function testArrayRepresentationOfDiffCanBeRenderedUsingMemoryEfficientLcsImplementation(array $expected, $from, $to)
  69. {
  70. $this->assertSame($expected, $this->differ->diffToArray($from, $to, new MemoryEfficientLongestCommonSubsequenceCalculator));
  71. }
  72. /**
  73. * @param string $expected
  74. * @param string $from
  75. * @param string $to
  76. * @dataProvider textProvider
  77. */
  78. public function testTextRepresentationOfDiffCanBeRenderedUsingMemoryEfficientLcsImplementation(string $expected, string $from, string $to)
  79. {
  80. $this->assertSame($expected, $this->differ->diff($from, $to, new MemoryEfficientLongestCommonSubsequenceCalculator));
  81. }
  82. /**
  83. * @param string $expected
  84. * @param string $from
  85. * @param string $to
  86. * @param string $header
  87. * @dataProvider headerProvider
  88. */
  89. public function testCustomHeaderCanBeUsed(string $expected, string $from, string $to, string $header)
  90. {
  91. $differ = new Differ(new UnifiedDiffOutputBuilder($header));
  92. $this->assertSame(
  93. $expected,
  94. $differ->diff($from, $to)
  95. );
  96. }
  97. public function headerProvider()
  98. {
  99. return [
  100. [
  101. "CUSTOM HEADER\n@@ @@\n-a\n+b\n",
  102. 'a',
  103. 'b',
  104. 'CUSTOM HEADER'
  105. ],
  106. [
  107. "CUSTOM HEADER\n@@ @@\n-a\n+b\n",
  108. 'a',
  109. 'b',
  110. "CUSTOM HEADER\n"
  111. ],
  112. [
  113. "CUSTOM HEADER\n\n@@ @@\n-a\n+b\n",
  114. 'a',
  115. 'b',
  116. "CUSTOM HEADER\n\n"
  117. ],
  118. [
  119. "@@ @@\n-a\n+b\n",
  120. 'a',
  121. 'b',
  122. ''
  123. ],
  124. ];
  125. }
  126. public function testTypesOtherThanArrayAndStringCanBePassed()
  127. {
  128. $this->assertSame(
  129. "--- Original\n+++ New\n@@ @@\n-1\n+2\n",
  130. $this->differ->diff(1, 2)
  131. );
  132. }
  133. /**
  134. * @param string $diff
  135. * @param Diff[] $expected
  136. * @dataProvider diffProvider
  137. */
  138. public function testParser(string $diff, array $expected)
  139. {
  140. $parser = new Parser;
  141. $result = $parser->parse($diff);
  142. $this->assertEquals($expected, $result);
  143. }
  144. public function arrayProvider(): array
  145. {
  146. return [
  147. [
  148. [
  149. ['a', self::REMOVED],
  150. ['b', self::ADDED]
  151. ],
  152. 'a',
  153. 'b'
  154. ],
  155. [
  156. [
  157. ['ba', self::REMOVED],
  158. ['bc', self::ADDED]
  159. ],
  160. 'ba',
  161. 'bc'
  162. ],
  163. [
  164. [
  165. ['ab', self::REMOVED],
  166. ['cb', self::ADDED]
  167. ],
  168. 'ab',
  169. 'cb'
  170. ],
  171. [
  172. [
  173. ['abc', self::REMOVED],
  174. ['adc', self::ADDED]
  175. ],
  176. 'abc',
  177. 'adc'
  178. ],
  179. [
  180. [
  181. ['ab', self::REMOVED],
  182. ['abc', self::ADDED]
  183. ],
  184. 'ab',
  185. 'abc'
  186. ],
  187. [
  188. [
  189. ['bc', self::REMOVED],
  190. ['abc', self::ADDED]
  191. ],
  192. 'bc',
  193. 'abc'
  194. ],
  195. [
  196. [
  197. ['abc', self::REMOVED],
  198. ['abbc', self::ADDED]
  199. ],
  200. 'abc',
  201. 'abbc'
  202. ],
  203. [
  204. [
  205. ['abcdde', self::REMOVED],
  206. ['abcde', self::ADDED]
  207. ],
  208. 'abcdde',
  209. 'abcde'
  210. ],
  211. 'same start' => [
  212. [
  213. [17, self::OLD],
  214. ['b', self::REMOVED],
  215. ['d', self::ADDED],
  216. ],
  217. [30 => 17, 'a' => 'b'],
  218. [30 => 17, 'c' => 'd'],
  219. ],
  220. 'same end' => [
  221. [
  222. [1, self::REMOVED],
  223. [2, self::ADDED],
  224. ['b', self::OLD],
  225. ],
  226. [1 => 1, 'a' => 'b'],
  227. [1 => 2, 'a' => 'b'],
  228. ],
  229. 'same start (2), same end (1)' => [
  230. [
  231. [17, self::OLD],
  232. [2, self::OLD],
  233. [4, self::REMOVED],
  234. ['a', self::ADDED],
  235. [5, self::ADDED],
  236. ['x', self::OLD],
  237. ],
  238. [30 => 17, 1 => 2, 2 => 4, 'z' => 'x'],
  239. [30 => 17, 1 => 2, 3 => 'a', 2 => 5, 'z' => 'x'],
  240. ],
  241. 'same' => [
  242. [
  243. ['x', self::OLD],
  244. ],
  245. ['z' => 'x'],
  246. ['z' => 'x'],
  247. ],
  248. 'diff' => [
  249. [
  250. ['y', self::REMOVED],
  251. ['x', self::ADDED],
  252. ],
  253. ['x' => 'y'],
  254. ['z' => 'x'],
  255. ],
  256. 'diff 2' => [
  257. [
  258. ['y', self::REMOVED],
  259. ['b', self::REMOVED],
  260. ['x', self::ADDED],
  261. ['d', self::ADDED],
  262. ],
  263. ['x' => 'y', 'a' => 'b'],
  264. ['z' => 'x', 'c' => 'd'],
  265. ],
  266. 'test line diff detection' => [
  267. [
  268. [
  269. "#Warning: Strings contain different line endings!\n",
  270. self::WARNING,
  271. ],
  272. [
  273. "<?php\r\n",
  274. self::REMOVED,
  275. ],
  276. [
  277. "<?php\n",
  278. self::ADDED,
  279. ],
  280. ],
  281. "<?php\r\n",
  282. "<?php\n",
  283. ],
  284. 'test line diff detection in array input' => [
  285. [
  286. [
  287. "#Warning: Strings contain different line endings!\n",
  288. self::WARNING,
  289. ],
  290. [
  291. "<?php\r\n",
  292. self::REMOVED,
  293. ],
  294. [
  295. "<?php\n",
  296. self::ADDED,
  297. ],
  298. ],
  299. ["<?php\r\n"],
  300. ["<?php\n"],
  301. ],
  302. ];
  303. }
  304. public function textProvider(): array
  305. {
  306. return [
  307. [
  308. "--- Original\n+++ New\n@@ @@\n-a\n+b\n",
  309. 'a',
  310. 'b'
  311. ],
  312. [
  313. "--- Original\n+++ New\n@@ @@\n-ba\n+bc\n",
  314. 'ba',
  315. 'bc'
  316. ],
  317. [
  318. "--- Original\n+++ New\n@@ @@\n-ab\n+cb\n",
  319. 'ab',
  320. 'cb'
  321. ],
  322. [
  323. "--- Original\n+++ New\n@@ @@\n-abc\n+adc\n",
  324. 'abc',
  325. 'adc'
  326. ],
  327. [
  328. "--- Original\n+++ New\n@@ @@\n-ab\n+abc\n",
  329. 'ab',
  330. 'abc'
  331. ],
  332. [
  333. "--- Original\n+++ New\n@@ @@\n-bc\n+abc\n",
  334. 'bc',
  335. 'abc'
  336. ],
  337. [
  338. "--- Original\n+++ New\n@@ @@\n-abc\n+abbc\n",
  339. 'abc',
  340. 'abbc'
  341. ],
  342. [
  343. "--- Original\n+++ New\n@@ @@\n-abcdde\n+abcde\n",
  344. 'abcdde',
  345. 'abcde'
  346. ],
  347. [
  348. "--- Original\n+++ New\n@@ @@\n-A\n+A1\n",
  349. "A\nB",
  350. "A1\nB",
  351. ],
  352. [
  353. <<<EOF
  354. --- Original
  355. +++ New
  356. @@ @@
  357. a
  358. -b
  359. +p
  360. @@ @@
  361. -j
  362. +w
  363. EOF
  364. ,
  365. "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk",
  366. "a\np\nc\nd\ne\nf\ng\nh\ni\nw\nk",
  367. ],
  368. [
  369. <<<EOF
  370. --- Original
  371. +++ New
  372. @@ @@
  373. -A
  374. +B
  375. EOF
  376. ,
  377. "A\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1",
  378. "B\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1",
  379. ],
  380. [
  381. "--- Original\n+++ New\n@@ @@\n #Warning: Strings contain different line endings!\n-<?php\r\n+<?php\n",
  382. "<?php\r\nA\n",
  383. "<?php\nA\n",
  384. ],
  385. [
  386. "--- Original\n+++ New\n@@ @@\n #Warning: Strings contain different line endings!\n-a\r\n+\n+c\r",
  387. "a\r\n",
  388. "\nc\r",
  389. ],
  390. ];
  391. }
  392. public function diffProvider(): array
  393. {
  394. $serialized_arr = <<<EOL
  395. a:1:{i:0;O:27:"SebastianBergmann\Diff\Diff":3:{s:33:"SebastianBergmann\Diff\Difffrom";s:7:"old.txt";s:31:"SebastianBergmann\Diff\Diffto";s:7:"new.txt";s:35:"SebastianBergmann\Diff\Diffchunks";a:3:{i:0;O:28:"SebastianBergmann\Diff\Chunk":5:{s:35:"SebastianBergmann\Diff\Chunkstart";i:1;s:40:"SebastianBergmann\Diff\ChunkstartRange";i:3;s:33:"SebastianBergmann\Diff\Chunkend";i:1;s:38:"SebastianBergmann\Diff\ChunkendRange";i:4;s:35:"SebastianBergmann\Diff\Chunklines";a:4:{i:0;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:1;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222111";}i:1;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:2;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:3;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}}}i:1;O:28:"SebastianBergmann\Diff\Chunk":5:{s:35:"SebastianBergmann\Diff\Chunkstart";i:5;s:40:"SebastianBergmann\Diff\ChunkstartRange";i:10;s:33:"SebastianBergmann\Diff\Chunkend";i:6;s:38:"SebastianBergmann\Diff\ChunkendRange";i:8;s:35:"SebastianBergmann\Diff\Chunklines";a:11:{i:0;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:1;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:2;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:3;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"+1121211";}i:4;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:5;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"-1111111";}i:6;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"-1111111";}i:7;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"-2222222";}i:8;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:9;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:10;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}}}i:2;O:28:"SebastianBergmann\Diff\Chunk":5:{s:35:"SebastianBergmann\Diff\Chunkstart";i:17;s:40:"SebastianBergmann\Diff\ChunkstartRange";i:5;s:33:"SebastianBergmann\Diff\Chunkend";i:16;s:38:"SebastianBergmann\Diff\ChunkendRange";i:6;s:35:"SebastianBergmann\Diff\Chunklines";a:6:{i:0;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:1;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:2;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:3;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"+2122212";}i:4;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:5;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}}}}}}
  396. EOL;
  397. return [
  398. [
  399. "--- old.txt 2014-11-04 08:51:02.661868729 +0300\n+++ new.txt 2014-11-04 08:51:02.665868730 +0300\n@@ -1,3 +1,4 @@\n+2222111\n 1111111\n 1111111\n 1111111\n@@ -5,10 +6,8 @@\n 1111111\n 1111111\n 1111111\n +1121211\n 1111111\n -1111111\n -1111111\n -2222222\n 2222222\n 2222222\n 2222222\n@@ -17,5 +16,6 @@\n 2222222\n 2222222\n 2222222\n +2122212\n 2222222\n 2222222\n",
  400. \unserialize($serialized_arr)
  401. ]
  402. ];
  403. }
  404. /**
  405. * @param string $expected
  406. * @param string $from
  407. * @param string $to
  408. * @param string $header
  409. * @dataProvider textForNoNonDiffLinesProvider
  410. */
  411. public function testDiffDoNotShowNonDiffLines(string $expected, string $from, string $to, string $header = '')
  412. {
  413. $differ = new Differ(new DiffOnlyOutputBuilder($header));
  414. $this->assertSame($expected, $differ->diff($from, $to));
  415. }
  416. public function textForNoNonDiffLinesProvider(): array
  417. {
  418. return [
  419. [
  420. " #Warning: Strings contain different line endings!\n-A\r\n+B\n",
  421. "A\r\n",
  422. "B\n",
  423. ],
  424. [
  425. "-A\n+B\n",
  426. "\nA",
  427. "\nB"
  428. ],
  429. [
  430. '',
  431. 'a',
  432. 'a'
  433. ],
  434. [
  435. "-A\n+C\n",
  436. "A\n\n\nB",
  437. "C\n\n\nB",
  438. ],
  439. [
  440. "header\n",
  441. 'a',
  442. 'a',
  443. 'header'
  444. ],
  445. [
  446. "header\n",
  447. 'a',
  448. 'a',
  449. "header\n"
  450. ],
  451. ];
  452. }
  453. public function testDiffToArrayInvalidFromType()
  454. {
  455. $this->expectException('\InvalidArgumentException');
  456. $this->expectExceptionMessageRegExp('#^"from" must be an array or string\.$#');
  457. $this->differ->diffToArray(null, '');
  458. }
  459. public function testDiffInvalidToType()
  460. {
  461. $this->expectException('\InvalidArgumentException');
  462. $this->expectExceptionMessageRegExp('#^"to" must be an array or string\.$#');
  463. $this->differ->diffToArray('', new \stdClass);
  464. }
  465. /**
  466. * @param array $expected
  467. * @param string $from
  468. * @param string $to
  469. * @param int $lineThreshold
  470. * @dataProvider provideGetCommonChunks
  471. */
  472. public function testGetCommonChunks(array $expected, string $from, string $to, int $lineThreshold = 5)
  473. {
  474. $output = new class extends AbstractChunkOutputBuilder {
  475. public function getDiff(array $diff): string
  476. {
  477. return '';
  478. }
  479. public function getChunks(array $diff, $lineThreshold)
  480. {
  481. return $this->getCommonChunks($diff, $lineThreshold);
  482. }
  483. };
  484. $this->assertSame(
  485. $expected,
  486. $output->getChunks($this->differ->diffToArray($from, $to), $lineThreshold)
  487. );
  488. }
  489. public function provideGetCommonChunks(): array
  490. {
  491. return[
  492. 'same (with default threshold)' => [
  493. [],
  494. 'A',
  495. 'A',
  496. ],
  497. 'same (threshold 0)' => [
  498. [0 => 0],
  499. 'A',
  500. 'A',
  501. 0,
  502. ],
  503. 'empty' => [
  504. [],
  505. '',
  506. '',
  507. ],
  508. 'single line diff' => [
  509. [],
  510. 'A',
  511. 'B',
  512. ],
  513. 'below threshold I' => [
  514. [],
  515. "A\nX\nC",
  516. "A\nB\nC",
  517. ],
  518. 'below threshold II' => [
  519. [],
  520. "A\n\n\n\nX\nC",
  521. "A\n\n\n\nB\nC",
  522. ],
  523. 'below threshold III' => [
  524. [0 => 5],
  525. "A\n\n\n\n\n\nB",
  526. "A\n\n\n\n\n\nA",
  527. ],
  528. 'same start' => [
  529. [0 => 5],
  530. "A\n\n\n\n\n\nX\nC",
  531. "A\n\n\n\n\n\nB\nC",
  532. ],
  533. 'same start long' => [
  534. [0 => 13],
  535. "\n\n\n\n\n\n\n\n\n\n\n\n\n\nA",
  536. "\n\n\n\n\n\n\n\n\n\n\n\n\n\nB",
  537. ],
  538. 'same part in between' => [
  539. [2 => 8],
  540. "A\n\n\n\n\n\n\nX\nY\nZ\n\n",
  541. "B\n\n\n\n\n\n\nX\nA\nZ\n\n",
  542. ],
  543. 'same trailing' => [
  544. [2 => 14],
  545. "A\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
  546. "B\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
  547. ],
  548. 'same part in between, same trailing' => [
  549. [2 => 7, 10 => 15],
  550. "A\n\n\n\n\n\n\nA\n\n\n\n\n\n\n",
  551. "B\n\n\n\n\n\n\nB\n\n\n\n\n\n\n",
  552. ],
  553. 'below custom threshold I' => [
  554. [],
  555. "A\n\nB",
  556. "A\n\nD",
  557. 2
  558. ],
  559. 'custom threshold I' => [
  560. [0 => 1],
  561. "A\n\nB",
  562. "A\n\nD",
  563. 1
  564. ],
  565. 'custom threshold II' => [
  566. [],
  567. "A\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
  568. "A\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
  569. 19
  570. ],
  571. [
  572. [3 => 9],
  573. "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk",
  574. "a\np\nc\nd\ne\nf\ng\nh\ni\nw\nk",
  575. ],
  576. [
  577. [0 => 5, 8 => 13],
  578. "A\nA\nA\nA\nA\nA\nX\nC\nC\nC\nC\nC\nC",
  579. "A\nA\nA\nA\nA\nA\nB\nC\nC\nC\nC\nC\nC",
  580. ],
  581. [
  582. [0 => 5, 8 => 13],
  583. "A\nA\nA\nA\nA\nA\nX\nC\nC\nC\nC\nC\nC\nX",
  584. "A\nA\nA\nA\nA\nA\nB\nC\nC\nC\nC\nC\nC\nY",
  585. ],
  586. ];
  587. }
  588. /**
  589. * @param array $expected
  590. * @param string $input
  591. * @dataProvider provideSplitStringByLinesCases
  592. */
  593. public function testSplitStringByLines(array $expected, string $input)
  594. {
  595. $reflection = new \ReflectionObject($this->differ);
  596. $method = $reflection->getMethod('splitStringByLines');
  597. $method->setAccessible(true);
  598. $this->assertSame($expected, $method->invoke($this->differ, $input));
  599. }
  600. public function provideSplitStringByLinesCases(): array
  601. {
  602. return [
  603. [
  604. [],
  605. ''
  606. ],
  607. [
  608. ['a'],
  609. 'a'
  610. ],
  611. [
  612. ["a\n"],
  613. "a\n"
  614. ],
  615. [
  616. ["a\r"],
  617. "a\r"
  618. ],
  619. [
  620. ["a\r\n"],
  621. "a\r\n"
  622. ],
  623. [
  624. ["\n"],
  625. "\n"
  626. ],
  627. [
  628. ["\r"],
  629. "\r"
  630. ],
  631. [
  632. ["\r\n"],
  633. "\r\n"
  634. ],
  635. [
  636. [
  637. "A\n",
  638. "B\n",
  639. "\n",
  640. "C\n"
  641. ],
  642. "A\nB\n\nC\n",
  643. ],
  644. [
  645. [
  646. "A\r\n",
  647. "B\n",
  648. "\n",
  649. "C\r"
  650. ],
  651. "A\r\nB\n\nC\r",
  652. ],
  653. [
  654. [
  655. "\n",
  656. "A\r\n",
  657. "B\n",
  658. "\n",
  659. 'C'
  660. ],
  661. "\nA\r\nB\n\nC",
  662. ],
  663. ];
  664. }
  665. /**
  666. * @param string $expected
  667. * @param string $from
  668. * @param string $to
  669. * @dataProvider provideDiffWithLineNumbers
  670. */
  671. public function testDiffWithLineNumbers($expected, $from, $to)
  672. {
  673. $differ = new Differ(new UnifiedDiffOutputBuilder("--- Original\n+++ New\n", true));
  674. $this->assertSame($expected, $differ->diff($from, $to));
  675. }
  676. public function provideDiffWithLineNumbers(): array
  677. {
  678. return [
  679. 'diff line 1 non_patch_compat' => [
  680. '--- Original
  681. +++ New
  682. @@ -1 +1 @@
  683. -AA
  684. +BA
  685. ',
  686. 'AA',
  687. 'BA',
  688. ],
  689. 'diff line +1 non_patch_compat' => [
  690. '--- Original
  691. +++ New
  692. @@ -1 +1,2 @@
  693. -AZ
  694. +
  695. +B
  696. ',
  697. 'AZ',
  698. "\nB",
  699. ],
  700. 'diff line -1 non_patch_compat' => [
  701. '--- Original
  702. +++ New
  703. @@ -1,2 +1 @@
  704. -
  705. -AF
  706. +B
  707. ',
  708. "\nAF",
  709. 'B',
  710. ],
  711. 'II non_patch_compat' => [
  712. '--- Original
  713. +++ New
  714. @@ -1,2 +1 @@
  715. -
  716. -
  717. '
  718. ,
  719. "\n\nA\n1",
  720. "A\n1",
  721. ],
  722. 'diff last line II - no trailing linebreak non_patch_compat' => [
  723. '--- Original
  724. +++ New
  725. @@ -8 +8 @@
  726. -E
  727. +B
  728. ',
  729. "A\n\n\n\n\n\n\nE",
  730. "A\n\n\n\n\n\n\nB",
  731. ],
  732. [
  733. "--- Original\n+++ New\n@@ -1,2 +1 @@\n \n-\n",
  734. "\n\n",
  735. "\n",
  736. ],
  737. 'diff line endings non_patch_compat' => [
  738. "--- Original\n+++ New\n@@ -1 +1 @@\n #Warning: Strings contain different line endings!\n-<?php\r\n+<?php\n",
  739. "<?php\r\n",
  740. "<?php\n",
  741. ],
  742. 'same non_patch_compat' => [
  743. '--- Original
  744. +++ New
  745. ',
  746. "AT\n",
  747. "AT\n",
  748. ],
  749. [
  750. '--- Original
  751. +++ New
  752. @@ -1 +1 @@
  753. -b
  754. +a
  755. ',
  756. "b\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
  757. "a\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  758. ],
  759. 'diff line @1' => [
  760. '--- Original
  761. +++ New
  762. @@ -1,2 +1,2 @@
  763. ' . '
  764. -AG
  765. +B
  766. ',
  767. "\nAG\n",
  768. "\nB\n",
  769. ],
  770. 'same multiple lines' => [
  771. '--- Original
  772. +++ New
  773. @@ -1,3 +1,3 @@
  774. ' . '
  775. ' . '
  776. -V
  777. +B
  778. '
  779. ,
  780. "\n\nV\nC213",
  781. "\n\nB\nC213",
  782. ],
  783. 'diff last line I' => [
  784. '--- Original
  785. +++ New
  786. @@ -8 +8 @@
  787. -E
  788. +B
  789. ',
  790. "A\n\n\n\n\n\n\nE\n",
  791. "A\n\n\n\n\n\n\nB\n",
  792. ],
  793. 'diff line middle' => [
  794. '--- Original
  795. +++ New
  796. @@ -8 +8 @@
  797. -X
  798. +Z
  799. ',
  800. "A\n\n\n\n\n\n\nX\n\n\n\n\n\n\nAY",
  801. "A\n\n\n\n\n\n\nZ\n\n\n\n\n\n\nAY",
  802. ],
  803. 'diff last line III' => [
  804. '--- Original
  805. +++ New
  806. @@ -15 +15 @@
  807. -A
  808. +B
  809. ',
  810. "A\n\n\n\n\n\n\nA\n\n\n\n\n\n\nA\n",
  811. "A\n\n\n\n\n\n\nA\n\n\n\n\n\n\nB\n",
  812. ],
  813. [
  814. '--- Original
  815. +++ New
  816. @@ -1,7 +1,7 @@
  817. A
  818. -B
  819. +B1
  820. D
  821. E
  822. EE
  823. F
  824. -G
  825. +G1
  826. ',
  827. "A\nB\nD\nE\nEE\nF\nG\nH",
  828. "A\nB1\nD\nE\nEE\nF\nG1\nH",
  829. ],
  830. [
  831. '--- Original
  832. +++ New
  833. @@ -1 +1,2 @@
  834. Z
  835. +
  836. @@ -10 +11 @@
  837. -i
  838. +x
  839. ',
  840. 'Z
  841. a
  842. b
  843. c
  844. d
  845. e
  846. f
  847. g
  848. h
  849. i
  850. j',
  851. 'Z
  852. a
  853. b
  854. c
  855. d
  856. e
  857. f
  858. g
  859. h
  860. x
  861. j'
  862. ],
  863. [
  864. '--- Original
  865. +++ New
  866. @@ -1,5 +1,3 @@
  867. -
  868. -a
  869. +b
  870. A
  871. -a
  872. -
  873. +b
  874. ',
  875. "\na\nA\na\n\n\nA",
  876. "b\nA\nb\n\nA"
  877. ],
  878. [
  879. <<<EOF
  880. --- Original
  881. +++ New
  882. @@ -1,4 +1,2 @@
  883. -
  884. -
  885. a
  886. -b
  887. +p
  888. @@ -12 +10 @@
  889. -j
  890. +w
  891. EOF
  892. ,
  893. "\n\na\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk",
  894. "a\np\nc\nd\ne\nf\ng\nh\ni\nw\nk",
  895. ],
  896. [
  897. '--- Original
  898. +++ New
  899. @@ -11 +11 @@
  900. -A
  901. +C
  902. ',
  903. "E\n\n\n\n\nB\n\n\n\n\nA\n\n\n\n\n\n\n\n\nD1",
  904. "E\n\n\n\n\nB\n\n\n\n\nC\n\n\n\n\n\n\n\n\nD1",
  905. ],
  906. [
  907. '--- Original
  908. +++ New
  909. @@ -8 +8 @@
  910. -Z
  911. +U
  912. @@ -15 +15 @@
  913. -X
  914. +V
  915. @@ -22 +22 @@
  916. -Y
  917. +W
  918. @@ -29 +29 @@
  919. -W
  920. +X
  921. @@ -36 +36 @@
  922. -V
  923. +Y
  924. @@ -43 +43 @@
  925. -U
  926. +Z
  927. ',
  928. "\n\n\n\n\n\n\nZ\n\n\n\n\n\n\nX\n\n\n\n\n\n\nY\n\n\n\n\n\n\nW\n\n\n\n\n\n\nV\n\n\n\n\n\n\nU\n",
  929. "\n\n\n\n\n\n\nU\n\n\n\n\n\n\nV\n\n\n\n\n\n\nW\n\n\n\n\n\n\nX\n\n\n\n\n\n\nY\n\n\n\n\n\n\nZ\n"
  930. ],
  931. [
  932. <<<EOF
  933. --- Original
  934. +++ New
  935. @@ -1,2 +1,2 @@
  936. a
  937. -b
  938. +p
  939. @@ -10 +10 @@
  940. -j
  941. +w
  942. EOF
  943. ,
  944. "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk",
  945. "a\np\nc\nd\ne\nf\ng\nh\ni\nw\nk",
  946. ],
  947. [
  948. <<<EOF
  949. --- Original
  950. +++ New
  951. @@ -1 +1 @@
  952. -A
  953. +B
  954. EOF
  955. ,
  956. "A\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1",
  957. "B\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1",
  958. ],
  959. [
  960. "--- Original\n+++ New\n@@ -7 +7 @@\n-X\n+B\n",
  961. "A\nA\nA\nA\nA\nA\nX\nC\nC\nC\nC\nC\nC",
  962. "A\nA\nA\nA\nA\nA\nB\nC\nC\nC\nC\nC\nC",
  963. ],
  964. ];
  965. }
  966. public function testConstructorNull()
  967. {
  968. $this->assertAttributeInstanceOf(
  969. UnifiedDiffOutputBuilder::class,
  970. 'outputBuilder',
  971. new Differ(null)
  972. );
  973. }
  974. public function testConstructorString()
  975. {
  976. $this->assertAttributeInstanceOf(
  977. UnifiedDiffOutputBuilder::class,
  978. 'outputBuilder',
  979. new Differ("--- Original\n+++ New\n")
  980. );
  981. }
  982. public function testConstructorInvalidArgInt()
  983. {
  984. $this->expectException(InvalidArgumentException::class);
  985. $this->expectExceptionMessageRegExp('/^Expected builder to be an instance of DiffOutputBuilderInterface, <null> or a string, got integer "1"\.$/');
  986. new Differ(1);
  987. }
  988. public function testConstructorInvalidArgObject()
  989. {
  990. $this->expectException(InvalidArgumentException::class);
  991. $this->expectExceptionMessageRegExp('/^Expected builder to be an instance of DiffOutputBuilderInterface, <null> or a string, got instance of "SplFileInfo"\.$/');
  992. new Differ(new \SplFileInfo(__FILE__));
  993. }
  994. }