From b01e0a91958f3b0532471e243864ec42255da266 Mon Sep 17 00:00:00 2001 From: chdemko Date: Sat, 18 Feb 2012 20:50:34 +0100 Subject: [PATCH] Implementing a file patcher --- .../en-GB.lib_joomla_filesystem_patcher.ini | 13 + libraries/joomla/filesystem/patcher.php | 523 ++++++++++ .../filesystem/JFilesystemPatcherTest.php | 954 ++++++++++++++++++ 3 files changed, 1490 insertions(+) create mode 100644 libraries/joomla/filesystem/meta/language/en-GB/en-GB.lib_joomla_filesystem_patcher.ini create mode 100644 libraries/joomla/filesystem/patcher.php create mode 100644 tests/suite/joomla/filesystem/JFilesystemPatcherTest.php diff --git a/libraries/joomla/filesystem/meta/language/en-GB/en-GB.lib_joomla_filesystem_patcher.ini b/libraries/joomla/filesystem/meta/language/en-GB/en-GB.lib_joomla_filesystem_patcher.ini new file mode 100644 index 0000000000..f81e5ccebc --- /dev/null +++ b/libraries/joomla/filesystem/meta/language/en-GB/en-GB.lib_joomla_filesystem_patcher.ini @@ -0,0 +1,13 @@ +; Joomla! Project +; Copyright (C) 2005 - 2012 Open Source Matters. All rights reserved. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 - No BOM + +JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY="Failed source verification of file %s at line %d" +JLIB_FILESYSTEM_PATCHER_INVALID_DIFF="Invalid unified diff block" +JLIB_FILESYSTEM_PATCHER_INVALID_INPUT="Invalid input" +JLIB_FILESYSTEM_PATCHER_UNEXISING_SOURCE="Unexisting source file" +JLIB_FILESYSTEM_PATCHER_UNEXPECTED_ADD_LINE="Unexpected add line at line %d'" +JLIB_FILESYSTEM_PATCHER_UNEXPECTED_EOF="Unexpected end of file" +JLIB_FILESYSTEM_PATCHER_UNEXPECTED_REMOVE_LINE="Unexpected remove line at line %d" + diff --git a/libraries/joomla/filesystem/patcher.php b/libraries/joomla/filesystem/patcher.php new file mode 100644 index 0000000000..356ccdae2b --- /dev/null +++ b/libraries/joomla/filesystem/patcher.php @@ -0,0 +1,523 @@ +sources = array(); + $this->destinations = array(); + $this->removals = array(); + $this->patches = array(); + return $this; + } + + /** + * Apply the patches + * + * @throw RuntimeException + * + * @return integer the number of files patched + */ + public function apply() + { + foreach ($this->patches as $patch) + { + // Separate the input into lines + $lines = self::splitLines($patch['udiff']); + + // Loop for each header + while (self::findHeader($lines, $src, $dst)) + { + $done = false; + + if ($patch['strip'] === null) + { + $src = $patch['root'] . preg_replace('#^([^/]*/)*#', '', $src); + $dst = $patch['root'] . preg_replace('#^([^/]*/)*#', '', $dst); + } + else + { + $src = $patch['root'] . preg_replace('#^([^/]*/){' . (int) $patch['strip'] . '}#', '', $src); + $dst = $patch['root'] . preg_replace('#^([^/]*/){' . (int) $patch['strip'] . '}#', '', $dst); + } + + // Loop for each hunk of differences + while (self::findHunk($lines, $src_line, $src_size, $dst_line, $dst_size)) + { + $done = true; + + // Apply the hunk of differences + $this->applyHunk($lines, $src, $dst, $src_line, $src_size, $dst_line, $dst_size); + } + + // If no modifications were found, throw an exception + if (!$done) + { + throw new RuntimeException(JText::_('JLIB_FILESYSTEM_PATCHER_INVALID_DIFF')); + } + } + } + + // Initialize the counter + $done = 0; + + // Patch each destination file + foreach ($this->destinations as $file => $content) + { + if (JFile::write($file, implode("\n", $content))) + { + if (isset($this->sources[$file])) + { + $this->sources[$file] = $content; + } + $done++; + } + } + + // Remove each removed file + foreach ($this->removals as $file) + { + if (JFile::delete($file)) + { + if (isset($this->sources[$file])) + { + unset($this->sources[$file]); + } + $done++; + } + } + + // Clear the destinations cache + $this->destinations = array(); + + // Clear the removals + $this->removals = array(); + + // Clear the patches + $this->patches = array(); + return $done; + } + + /** + * Add a unified diff file to the patcher + * + * @param string $filename Path to the unified diff file + * @param string $root The files root path + * @param string $strip The number of '/' to strip + * + * @return JFilesystemPatch $this for chaining + * + * @since 12.1 + */ + public function addFile($filename, $root = JPATH_BASE, $strip = 0) + { + return $this->add(file_get_contents($filename), $root, $strip); + } + + /** + * Add a unified diff string to the patcher + * + * @param string $udiff Unified diff input string + * @param string $root The files root path + * @param string $strip The number of '/' to strip + * + * @return JFilesystemPatch $this for chaining + * + * @since 12.1 + */ + public function add($udiff, $root = JPATH_BASE, $strip = 0) + { + $this->patches[] = array( + 'udiff' => $udiff, + 'root' => isset($root) ? rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : '', + 'strip' => $strip + ); + return $this; + } + + /** + * Separate CR or CRLF lines + * + * @param string $data Input string + * + * @return array The lines of the inputdestination file + * + * @since 12.1 + */ + protected static function splitLines($data) + { + return preg_split(self::SPLIT, $data); + } + + /** + * Find the diff header + * + * The internal array pointer of $lines is on the next line after the finding + * + * @param array &$lines The udiff array of lines + * @param string &$src The source file + * @param string &$dst The destination file + * + * @return boolean TRUE in case of success, FALSE in case of failure + * + * @throw RuntimeException + */ + protected static function findHeader(&$lines, &$src, &$dst) + { + // Get the current line + $line = current($lines); + + // Search for the header + while ($line !== false && !preg_match(self::SRC_FILE, $line, $m)) + { + $line = next($lines); + } + if ($line === false) + { + // No header found, return false + return false; + } + else + { + // Set the source file + $src = $m[1]; + + // Advance to the next line + $line = next($lines); + if ($line === false) + { + throw new RuntimeException(JText::_('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_EOF')); + } + + // Search the destination file + if (!preg_match(self::DST_FILE, $line, $m)) + { + throw new RuntimeException(JText::_('JLIB_FILESYSTEM_PATCHER_INVALID_DIFF')); + } + + // Set the destination file + $dst = $m[1]; + + // Advance to the next line + if (next($lines) === false) + { + throw new RuntimeException(JText::_('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_EOF')); + } + return true; + } + } + + /** + * Find the next hunk of difference + * + * The internal array pointer of $lines is on the next line after the finding + * + * @param array &$lines The udiff array of lines + * @param string &$src_line The beginning of the patch for the source file + * @param string &$src_size The size of the patch for the source file + * @param string &$dst_line The beginning of the patch for the destination file + * @param string &$dst_size The size of the patch for the destination file + * + * @return boolean TRUE in case of success, false in case of failure + * + * @throw RuntimeException + */ + protected static function findHunk(&$lines, &$src_line, &$src_size, &$dst_line, &$dst_size) + { + $line = current($lines); + if (preg_match(self::HUNK, $line, $m)) + { + $src_line = (int) $m[1]; + if ($m[3] === '') + { + $src_size = 1; + } + else + { + $src_size = (int) $m[3]; + } + + $dst_line = (int) $m[4]; + if ($m[6] === '') + { + $dst_size = 1; + } + else + { + $dst_size = (int) $m[6]; + } + + if (next($lines) === false) + { + throw new RuntimeException(JText::_('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_EOF')); + } + + return true; + } + else + { + return false; + } + } + + /** + * Apply the patch + * + * @param array &$lines The udiff array of lines + * @param string $src The source file + * @param string $dst The destination file + * @param string $src_line The beginning of the patch for the source file + * @param string $src_size The size of the patch for the source file + * @param string $dst_line The beginning of the patch for the destination file + * @param string $dst_size The size of the patch for the destination file + * + * @return void + * + * @throw RuntimeException + */ + protected function applyHunk(&$lines, $src, $dst, $src_line, $src_size, $dst_line, $dst_size) + { + $src_line--; + $dst_line--; + $line = current($lines); + + // Source lines (old file) + $source = array(); + + // New lines (new file) + $destin = array(); + $src_left = $src_size; + $dst_left = $dst_size; + do + { + if (!isset($line[0])) + { + $source[] = ''; + $destin[] = ''; + $src_left--; + $dst_left--; + } + elseif ($line[0] == '-') + { + if ($src_left == 0) + { + throw new RuntimeException(JText::sprintf('JLIB_FILESYSTEM_PATCHER_REMOVE_LINE', key($lines))); + } + $source[] = substr($line, 1); + $src_left--; + } + elseif ($line[0] == '+') + { + if ($dst_left == 0) + { + throw new RuntimeException(JText::sprintf('JLIB_FILESYSTEM_PATCHER_ADD_LINE', key($lines))); + } + $destin[] = substr($line, 1); + $dst_left--; + } + elseif ($line != '\\ No newline at end of file') + { + $line = substr($line, 1); + $source[] = $line; + $destin[] = $line; + $src_left--; + $dst_left--; + } + if ($src_left == 0 && $dst_left == 0) + { + + // Now apply the patch, finally! + if ($src_size > 0) + { + $src_lines = & $this->getSource($src); + if (!isset($src_lines)) + { + throw new RuntimeException(JText::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXISING_SOURCE', $src)); + } + } + if ($dst_size > 0) + { + if ($src_size > 0) + { + $dst_lines = & $this->getDestination($dst, $src); + $src_bottom = $src_line + count($source); + $dst_bottom = $dst_line + count($destin); + for ($l = $src_line;$l < $src_bottom;$l++) + { + if ($src_lines[$l] != $source[$l - $src_line]) + { + throw new RuntimeException(JText::sprintf('JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY', $src, $l)); + } + } + array_splice($dst_lines, $dst_line, count($source), $destin); + } + else + { + $this->destinations[$dst] = $destin; + } + } + else + { + $this->removals[] = $src; + } + next($lines); + return; + } + $line = next($lines); + } + while ($line !== false); + throw new RuntimeException(JText::_('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_EOF')); + } + + /** + * Get the lines of a source file + * + * @param string $src The path of a file + * + * @return array The lines of the source file + * + * @since 12.1 + */ + protected function &getSource($src) + { + if (!isset($this->sources[$src])) + { + if (is_readable($src)) + { + $this->sources[$src] = self::splitLines(file_get_contents($src)); + } + else + { + $this->sources[$src] = null; + } + } + return $this->sources[$src]; + } + + /** + * Get the lines of a destination file + * + * @param string $dst The path of a destination file + * @param string $src The path of a source file + * + * @return array The lines of the destination file + * + * @since 12.1 + */ + protected function &getDestination($dst, $src) + { + if (!isset($this->destinations[$dst])) + { + $this->destinations[$dst] = $this->getSource($src); + } + return $this->destinations[$dst]; + } +} diff --git a/tests/suite/joomla/filesystem/JFilesystemPatcherTest.php b/tests/suite/joomla/filesystem/JFilesystemPatcherTest.php new file mode 100644 index 0000000000..ff7ecfb99d --- /dev/null +++ b/tests/suite/joomla/filesystem/JFilesystemPatcherTest.php @@ -0,0 +1,954 @@ +_cleanupTestFiles(); + + // Make some test files and folders + mkdir(JPath::clean(JPATH_TESTS . '/tmp/patcher'), 0777, true); + } + + /** + * Remove created files + * + * @return void + * + * @since 12.1 + */ + protected function tearDown() + { + $this->_cleanupTestFiles(); + } + + /** + * Convenience method to cleanup before and after test + * + * @return void + * + * @since 12.1 + */ + private function _cleanupTestFiles() + { + $this->_cleanupFile(JPath::clean(JPATH_TESTS . '/tmp/patcher/lao2tzu.diff')); + $this->_cleanupFile(JPath::clean(JPATH_TESTS . '/tmp/patcher/lao')); + $this->_cleanupFile(JPath::clean(JPATH_TESTS . '/tmp/patcher/tzu')); + $this->_cleanupFile(JPath::clean(JPATH_TESTS . '/tmp/patcher')); + } + + /** + * Convenience method to clean up for files test + * + * @param string $path The path to clean + * + * @return void + * + * @since 12.1 + */ + private function _cleanupFile($path) + { + if (file_exists($path)) + { + if (is_file($path)) + { + unlink($path); + } + elseif (is_dir($path)) + { + rmdir($path); + } + } + } + + /** + * Data provider for testAdd + * + * @return array + * + * @since 12.1 + */ + public function addData() + { + $udiff = 'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,7 +1,6 @@ +-The Way that can be told of is not the eternal Way; +-The name that can be named is not the eternal name. + The Nameless is the origin of Heaven and Earth; +-The Named is the mother of all things. ++The named is the mother of all things. ++ + Therefore let there always be non-being, + so we may see their subtlety, + And let there always be being, +@@ -9,4 +8,7 @@ + The two are the same, + But after they are produced, + they have different names. ++They both may be called deep and profound. ++Deeper and more profound, ++The door of all subtleties! +'; + return array( + array( + $udiff, + JPATH_TESTS . '/tmp/patcher', + 0, + array( + array( + 'udiff' => $udiff, + 'root' => JPATH_TESTS . '/tmp/patcher/', + 'strip' => 0 + ) + ) + ), + array( + $udiff, + JPATH_TESTS . '/tmp/patcher/', + 0, + array( + array( + 'udiff' => $udiff, + 'root' => JPATH_TESTS . '/tmp/patcher/', + 'strip' => 0 + ) + ) + ), + array( + $udiff, + null, + 0, + array( + array( + 'udiff' => $udiff, + 'root' => '', + 'strip' => 0 + ) + ) + ), + array( + $udiff, + '', + 0, + array( + array( + 'udiff' => $udiff, + 'root' => '/', + 'strip' => 0 + ) + ) + ), + ); + } + + /** + * Test JFilesystemPatcher::add add a unified diff string to the patcher + * + * @param string $udiff Unified diff input string + * @param string $root The files root path + * @param string $strip The number of '/' to strip + * @param array $expected The expected array patches + * + * @return void + * + * @since 12.1 + * + * @dataProvider JFilesystemPatcherTest::addData + */ + public function testAdd($udiff, $root, $strip, $expected) + { + $patcher = JFilesystemPatcher::getInstance()->reset(); + $patcher->add($udiff, $root, $strip); + $this->assertAttributeEquals( + $expected, + 'patches', + $patcher, + 'Line:' . __LINE__ . ' The patcher cannot add the unified diff string.' + ); + } + + /** + * Test JFilesystemPatcher::addFile add a unified diff file to the patcher + * + * @return void + * + * @since 12.1 + */ + public function testAddFile() + { + $udiff = 'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,7 +1,6 @@ +-The Way that can be told of is not the eternal Way; +-The name that can be named is not the eternal name. + The Nameless is the origin of Heaven and Earth; +-The Named is the mother of all things. ++The named is the mother of all things. ++ + Therefore let there always be non-being, + so we may see their subtlety, + And let there always be being, +@@ -9,4 +8,7 @@ + The two are the same, + But after they are produced, + they have different names. ++They both may be called deep and profound. ++Deeper and more profound, ++The door of all subtleties! +'; + file_put_contents(JPATH_TESTS . '/tmp/patcher/lao2tzu.diff', $udiff); + $patcher = JFilesystemPatcher::getInstance()->reset(); + $patcher->addFile(JPATH_TESTS . '/tmp/patcher/lao2tzu.diff', JPATH_TESTS . '/tmp/patcher'); + $this->assertAttributeEquals( + array( + array( + 'udiff' => $udiff, + 'root' => JPATH_TESTS . '/tmp/patcher/', + 'strip' => 0 + ) + ), + 'patches', + $patcher, + 'Line:' . __LINE__ . ' The patcher cannot add the unified diff file.' + ); + } + + /** + * JFilesystemPatcher::reset reset the patcher to its initial state + * + * @return void + */ + public function testReset() + { + $udiff = 'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,7 +1,6 @@ +-The Way that can be told of is not the eternal Way; +-The name that can be named is not the eternal name. + The Nameless is the origin of Heaven and Earth; +-The Named is the mother of all things. ++The named is the mother of all things. ++ + Therefore let there always be non-being, + so we may see their subtlety, + And let there always be being, +@@ -9,4 +8,7 @@ + The two are the same, + But after they are produced, + they have different names. ++They both may be called deep and profound. ++Deeper and more profound, ++The door of all subtleties! +'; + $patcher = JFilesystemPatcher::getInstance()->reset(); + $patcher->add($udiff, __DIR__ . '/patcher/'); + $this->assertEquals( + $patcher->reset(), + $patcher, + 'Line:' . __LINE__ . ' The reset method does not return $this for chaining.' + ); + $this->assertAttributeEquals( + array(), + 'sources', + $patcher, + 'Line:' . __LINE__ . ' The patcher has not been reset.' + ); + $this->assertAttributeEquals( + array(), + 'destinations', + $patcher, + 'Line:' . __LINE__ . ' The patcher has not been reset.' + ); + $this->assertAttributeEquals( + array(), + 'removals', + $patcher, + 'Line:' . __LINE__ . ' The patcher has not been reset.' + ); + $this->assertAttributeEquals( + array(), + 'patches', + $patcher, + 'Line:' . __LINE__ . ' The patcher has not been reset.' + ); + } + + /** + * Data provider for testApply + * + * @return array + * + * @since 12.1 + */ + public function applyData() + { + return array( + // Test classical feature + 'Test classical feature' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,7 +1,6 @@ +-The Way that can be told of is not the eternal Way; +-The name that can be named is not the eternal name. + The Nameless is the origin of Heaven and Earth; +-The Named is the mother of all things. ++The named is the mother of all things. ++ + Therefore let there always be non-being, + so we may see their subtlety, + And let there always be being, +@@ -9,4 +8,7 @@ + The two are the same, + But after they are produced, + they have different names. ++They both may be called deep and profound. ++Deeper and more profound, ++The door of all subtleties! +', + JPATH_TESTS . '/tmp/patcher', + 0, + array( + JPATH_TESTS . '/tmp/patcher/lao' => +'The Way that can be told of is not the eternal Way; +The name that can be named is not the eternal name. +The Nameless is the origin of Heaven and Earth; +The Named is the mother of all things. +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +' + ), + array( + JPATH_TESTS . '/tmp/patcher/tzu' => +'The Nameless is the origin of Heaven and Earth; +The named is the mother of all things. + +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +They both may be called deep and profound. +Deeper and more profound, +The door of all subtleties! +' + ), + 1, + false + ), + + // Test truncated hunk + 'Test truncated hunk' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1 +1 @@ +-The Way that can be told of is not the eternal Way; ++The named is the mother of all things. +', + JPATH_TESTS . '/tmp/patcher', + 0, + array( + JPATH_TESTS . '/tmp/patcher/lao' => +'The Way that can be told of is not the eternal Way; +The name that can be named is not the eternal name. +The Nameless is the origin of Heaven and Earth; +The Named is the mother of all things. +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +' + ), + array( + JPATH_TESTS . '/tmp/patcher/tzu' => +'The named is the mother of all things. +The name that can be named is not the eternal name. +The Nameless is the origin of Heaven and Earth; +The Named is the mother of all things. +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +' + ), + 1, + false + ), + + // Test strip is null + 'Test strip is null' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,7 +1,6 @@ +-The Way that can be told of is not the eternal Way; +-The name that can be named is not the eternal name. + The Nameless is the origin of Heaven and Earth; +-The Named is the mother of all things. ++The named is the mother of all things. ++ + Therefore let there always be non-being, + so we may see their subtlety, + And let there always be being, +@@ -9,4 +8,7 @@ + The two are the same, + But after they are produced, + they have different names. ++They both may be called deep and profound. ++Deeper and more profound, ++The door of all subtleties! +', + JPATH_TESTS . '/tmp/patcher', + null, + array( + JPATH_TESTS . '/tmp/patcher/lao' => +'The Way that can be told of is not the eternal Way; +The name that can be named is not the eternal name. +The Nameless is the origin of Heaven and Earth; +The Named is the mother of all things. +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +' + ), + array( + JPATH_TESTS . '/tmp/patcher/tzu' => +'The Nameless is the origin of Heaven and Earth; +The named is the mother of all things. + +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +They both may be called deep and profound. +Deeper and more profound, +The door of all subtleties! +' + ), + 1, + false + ), + + // Test strip is different of 0 + 'Test strip is different of 0' => array( +'Index: lao +=================================================================== +--- /path/to/lao 2011-09-21 16:05:45.086909120 +0200 ++++ /path/to/tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,7 +1,6 @@ +-The Way that can be told of is not the eternal Way; +-The name that can be named is not the eternal name. + The Nameless is the origin of Heaven and Earth; +-The Named is the mother of all things. ++The named is the mother of all things. ++ + Therefore let there always be non-being, + so we may see their subtlety, + And let there always be being, +@@ -9,4 +8,7 @@ + The two are the same, + But after they are produced, + they have different names. ++They both may be called deep and profound. ++Deeper and more profound, ++The door of all subtleties! +', + JPATH_TESTS . '/tmp/patcher', + 3, + array( + JPATH_TESTS . '/tmp/patcher/lao' => +'The Way that can be told of is not the eternal Way; +The name that can be named is not the eternal name. +The Nameless is the origin of Heaven and Earth; +The Named is the mother of all things. +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +' + ), + array( + JPATH_TESTS . '/tmp/patcher/tzu' => +'The Nameless is the origin of Heaven and Earth; +The named is the mother of all things. + +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +They both may be called deep and profound. +Deeper and more profound, +The door of all subtleties! +' + ), + 1, + false + ), + + // Test create file + 'Test create file' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -0,0 +1,14 @@ ++The Nameless is the origin of Heaven and Earth; ++The named is the mother of all things. ++ ++Therefore let there always be non-being, ++ so we may see their subtlety, ++And let there always be being, ++ so we may see their outcome. ++The two are the same, ++But after they are produced, ++ they have different names. ++They both may be called deep and profound. ++Deeper and more profound, ++The door of all subtleties! ++ +', + JPATH_TESTS . '/tmp/patcher', + 0, + array(), + array( + JPATH_TESTS . '/tmp/patcher/tzu' => +'The Nameless is the origin of Heaven and Earth; +The named is the mother of all things. + +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +They both may be called deep and profound. +Deeper and more profound, +The door of all subtleties! +' + ), + 1, + false + ), + + // Test patch itself + 'Test patch itself' => array( +'Index: lao +=================================================================== +--- tzu 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,7 +1,6 @@ +-The Way that can be told of is not the eternal Way; +-The name that can be named is not the eternal name. + The Nameless is the origin of Heaven and Earth; +-The Named is the mother of all things. ++The named is the mother of all things. ++ + Therefore let there always be non-being, + so we may see their subtlety, + And let there always be being, +@@ -9,4 +8,7 @@ + The two are the same, + But after they are produced, + they have different names. ++They both may be called deep and profound. ++Deeper and more profound, ++The door of all subtleties! +', + JPATH_TESTS . '/tmp/patcher', + 0, + array( + JPATH_TESTS . '/tmp/patcher/tzu' => +'The Way that can be told of is not the eternal Way; +The name that can be named is not the eternal name. +The Nameless is the origin of Heaven and Earth; +The Named is the mother of all things. +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +' + ), + array( + JPATH_TESTS . '/tmp/patcher/tzu' => +'The Nameless is the origin of Heaven and Earth; +The named is the mother of all things. + +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +They both may be called deep and profound. +Deeper and more profound, +The door of all subtleties! +' + ), + 1, + false + ), + + // Test delete + 'Test delete' => array( +'Index: lao +=================================================================== +--- tzu 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,11 +1,0 @@ +-The Way that can be told of is not the eternal Way; +-The name that can be named is not the eternal name. +-The Nameless is the origin of Heaven and Earth; +-The Named is the mother of all things. +-Therefore let there always be non-being, +- so we may see their subtlety, +-And let there always be being, +- so we may see their outcome. +-The two are the same, +-But after they are produced, +- they have different names. +', + JPATH_TESTS . '/tmp/patcher', + 0, + array( + JPATH_TESTS . '/tmp/patcher/tzu' => +'The Way that can be told of is not the eternal Way; +The name that can be named is not the eternal name. +The Nameless is the origin of Heaven and Earth; +The Named is the mother of all things. +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +' + ), + array( + JPATH_TESTS . '/tmp/patcher/tzu' => null + ), + 1, + false + ), + + // Test unexpected eof after header + 'Test unexpected eof after header 1' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +', + JPATH_TESTS . '/tmp/patcher', + 0, + array(), + array(), + 1, + 'RuntimeException' + ), + + // Test unexpected eof after header + 'Test unexpected eof after header 2' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200', + JPATH_TESTS . '/tmp/patcher', + 0, + array(), + array(), + 1, + 'RuntimeException' + ), + + // Test unexpected eof in header + 'Test unexpected eof in header' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200', + JPATH_TESTS . '/tmp/patcher', + 0, + array(), + array(), + 1, + 'RuntimeException' + ), + + // Test invalid diff in header + 'Test invalid diff in header' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 +', + JPATH_TESTS . '/tmp/patcher', + 0, + array(), + array(), + 1, + 'RuntimeException' + ), + + // Test unexpected eof after hunk 1 + 'Test unexpected eof after hunk 1' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,11 +1,0 @@', + JPATH_TESTS . '/tmp/patcher', + 0, + array(), + array(), + 1, + 'RuntimeException' + ), + + // Test unexpected eof after hunk 2 + 'Test unexpected eof after hunk 2' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,11 +1,11 @@ ++The Way that can be told of is not the eternal Way; ++The name that can be named is not the eternal name. +-The Nameless is the origin of Heaven and Earth; +', + JPATH_TESTS . '/tmp/patcher', + 0, + array(), + array(), + 1, + 'RuntimeException' + ), + + // Test unexpected remove line + 'Test unexpected remove line' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,1 +1,1 @@ +-The Way that can be told of is not the eternal Way; +-The name that can be named is not the eternal name. ++The Nameless is the origin of Heaven and Earth; +', + JPATH_TESTS . '/tmp/patcher', + 0, + array(), + array(), + 1, + 'RuntimeException' + ), + + // Test unexpected add line + 'Test unexpected add line' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,1 +1,1 @@ ++The Way that can be told of is not the eternal Way; ++The name that can be named is not the eternal name. +-The Nameless is the origin of Heaven and Earth; +', + JPATH_TESTS . '/tmp/patcher', + 0, + array(), + array(), + 1, + 'RuntimeException' + ), + + // Test unexisting source + 'Test unexisting source' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,7 +1,6 @@ +-The Way that can be told of is not the eternal Way; +-The name that can be named is not the eternal name. + The Nameless is the origin of Heaven and Earth; +-The Named is the mother of all things. ++The named is the mother of all things. ++ + Therefore let there always be non-being, + so we may see their subtlety, + And let there always be being, +@@ -9,4 +8,7 @@ + The two are the same, + But after they are produced, + they have different names. ++They both may be called deep and profound. ++Deeper and more profound, ++The door of all subtleties! +', + JPATH_TESTS . '/tmp/patcher', + 0, + array(), + array(), + 1, + 'RuntimeException' + ), + + // Test failed verify + 'Test failed verify' => array( +'Index: lao +=================================================================== +--- lao 2011-09-21 16:05:45.086909120 +0200 ++++ tzu 2011-09-21 16:05:41.156878938 +0200 +@@ -1,7 +1,6 @@ +-The Way that can be told of is not the eternal Way; +-The name that can be named is not the eternal name. + The Nameless is the origin of Heaven and Earth; +-The Named is the mother of all things. ++The named is the mother of all things. ++ + Therefore let there always be non-being, + so we may see their subtlety, + And let there always be being, +@@ -9,4 +8,7 @@ + The two are the same, + But after they are produced, + they have different names. ++They both may be called deep and profound. ++Deeper and more profound, ++The door of all subtleties! +', + JPATH_TESTS . '/tmp/patcher', + 0, + array( + JPATH_TESTS . '/tmp/patcher/lao' => '' + ), + array(), + 1, + 'RuntimeException' + ), + ); + } + + /** + * JFilesystemPatcher::apply apply the patches + * + * @param string $udiff Unified diff input string + * @param string $root The files root path + * @param string $strip The number of '/' to strip + * @param array $sources The source files + * @param array $destinations The destinations files + * @param integer $result The number of files patched + * @param mixed $throw The exception throw, false for no exception + * + * @return void + * + * @since 12.1 + * + * @dataProvider JFilesystemPatcherTest::applyData + */ + public function testApply($udiff, $root, $strip, $sources, $destinations, $result, $throw) + { + if ($throw) + { + $this->setExpectedException($throw); + } + + foreach ($sources as $path => $content) + { + file_put_contents($path, $content); + } + $patcher = JFilesystemPatcher::getInstance()->reset(); + $patcher->add($udiff, $root, $strip); + $this->assertEquals( + $result, + $patcher->apply(), + 'Line:' . __LINE__ . ' The patcher did not patch ' . $result . ' file(s).' + ); + foreach ($destinations as $path => $content) + { + if (is_null($content)) + { + $this->assertFalse( + is_file($path), + 'Line:' . __LINE__ . ' The patcher did not succeed in patching ' . $path + ); + } + else + { + $this->assertEquals( + $content, + file_get_contents($path), + 'Line:' . __LINE__ . ' The patcher did not succeed in patching ' . $path + ); + } + } + } +}