소스 검색

Update PEAR Package Extractor to use 'task:replace', 'phprelease' commands and install role='script' files
Add PearInstaller
Change PEAR packages type from 'library' to 'pear-library' and dist type from 'pear' to 'file'
Remove PearDownloader
Refactor Channel Installer

Alexey Prilipko 12 년 전
부모
커밋
ac3cebc633

+ 0 - 52
src/Composer/Downloader/PearDownloader.php

@@ -1,52 +0,0 @@
-<?php
-
-/*
- * This file is part of Composer.
- *
- * (c) Nils Adermann <naderman@naderman.de>
- *     Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Composer\Downloader;
-
-use Composer\Package\PackageInterface;
-
-/**
- * Downloader for pear packages
- *
- * @author Jordi Boggiano <j.boggiano@seld.be>
- * @author Kirill chEbba Chebunin <iam@chebba.org>
- */
-class PearDownloader extends FileDownloader
-{
-    /**
-     * {@inheritDoc}
-     */
-    public function download(PackageInterface $package, $path)
-    {
-        parent::download($package, $path);
-
-        $fileName = $this->getFileName($package, $path);
-        if ($this->io->isVerbose()) {
-            $this->io->write('    Installing PEAR package');
-        }
-        try {
-            $pearExtractor = new PearPackageExtractor($fileName);
-            $pearExtractor->extractTo($path);
-
-            if ($this->io->isVerbose()) {
-                $this->io->write('    Cleaning up');
-            }
-            unlink($fileName);
-        } catch (\Exception $e) {
-            // clean up
-            $this->filesystem->removeDirectory($path);
-            throw $e;
-        }
-
-        $this->io->write('');
-    }
-}

+ 89 - 28
src/Composer/Downloader/PearPackageExtractor.php

@@ -35,22 +35,22 @@ class PearPackageExtractor
             throw new \UnexpectedValueException('PEAR package file is not found at '.$file);
         }
 
+        $this->filesystem = new Filesystem();
         $this->file = $file;
     }
 
     /**
      * Installs PEAR source files according to package.xml definitions and removes extracted files
      *
-     * @param $file   string path to downloaded PEAR archive file
-     * @param $target string target install location. all source installation would be performed relative to target path.
-     * @param $role   string type of files to install. default role for PEAR source files are 'php'.
-     *
+     * @param  string                    $target target install location. all source installation would be performed relative to target path.
+     * @param  array                     $roles  types of files to install. default role for PEAR source files are 'php'.
+     * @param  array                     $vars   used for replacement tasks
      * @throws \RuntimeException
+     * @throws \UnexpectedValueException
+     *
      */
-    public function extractTo($target, $role = 'php')
+    public function extractTo($target, array $roles = array('php' => '/', 'script' => '/bin'), $vars = array())
     {
-        $this->filesystem = new Filesystem();
-
         $extractionPath = $target.'/tarball';
 
         try {
@@ -61,8 +61,8 @@ class PearPackageExtractor
                 throw new \RuntimeException('Invalid PEAR package. It must contain package.xml file.');
             }
 
-            $fileCopyActions = $this->buildCopyActions($extractionPath, $role);
-            $this->copyFiles($fileCopyActions, $extractionPath, $target);
+            $fileCopyActions = $this->buildCopyActions($extractionPath, $roles, $vars);
+            $this->copyFiles($fileCopyActions, $extractionPath, $target, $roles, $vars);
             $this->filesystem->removeDirectory($extractionPath);
         } catch (\Exception $exception) {
             throw new \UnexpectedValueException(sprintf('Failed to extract PEAR package %s to %s. Reason: %s', $this->file, $target, $exception->getMessage()), 0, $exception);
@@ -72,20 +72,24 @@ class PearPackageExtractor
     /**
      * Perform copy actions on files
      *
-     * @param $files array array('from', 'to') with relative paths
+     * @param array $files array of copy actions ('from', 'to') with relative paths
      * @param $source string path to source dir.
      * @param $target string path to destination dir
+     * @param array $roles array [role => roleRoot] relative root for files having that role
+     * @param array $vars  list of values can be used for replacement tasks
      */
-    private function copyFiles($files, $source, $target)
+    private function copyFiles($files, $source, $target, $roles, $vars)
     {
         foreach ($files as $file) {
             $from = $this->combine($source, $file['from']);
-            $to = $this->combine($target, $file['to']);
-            $this->copyFile($from, $to);
+            $to = $this->combine($target, $roles[$file['role']]);
+            $to = $this->combine($to, $file['to']);
+            $tasks = $file['tasks'];
+            $this->copyFile($from, $to, $tasks, $vars);
         }
     }
 
-    private function copyFile($from, $to)
+    private function copyFile($from, $to, $tasks, $vars)
     {
         if (!is_file($from)) {
             throw new \RuntimeException('Invalid PEAR package. package.xml defines file that is not located inside tarball.');
@@ -93,7 +97,24 @@ class PearPackageExtractor
 
         $this->filesystem->ensureDirectoryExists(dirname($to));
 
-        if (!copy($from, $to)) {
+        if (0 == count($tasks)) {
+            $copied = copy($from, $to);
+        } else {
+            $content = file_get_contents($from);
+            $replacements = array();
+            foreach ($tasks as $task) {
+                $pattern = $task['from'];
+                $varName = $task['to'];
+                if (isset($vars[$varName])) {
+                    $replacements[$pattern] = $vars[$varName];
+                }
+            }
+            $content = strtr($content, $replacements);
+
+            $copied = file_put_contents($to, $content);
+        }
+
+        if (false === $copied) {
             throw new \RuntimeException(sprintf('Failed to copy %s to %s', $from, $to));
         }
     }
@@ -107,7 +128,7 @@ class PearPackageExtractor
      *  path, and target is destination of file (also relative to $source path)
      * @throws \RuntimeException
      */
-    private function buildCopyActions($source, $role)
+    private function buildCopyActions($source, array $roles, $vars)
     {
         /** @var $package \SimpleXmlElement */
         $package = simplexml_load_file($this->combine($source, 'package.xml'));
@@ -120,13 +141,18 @@ class PearPackageExtractor
             $packageName = (string) $package->name;
             $packageVersion = (string) $package->release->version;
             $sourceDir = $packageName . '-' . $packageVersion;
-            $result = $this->buildSourceList10($children, $role, $sourceDir);
+            $result = $this->buildSourceList10($children, $roles, $sourceDir);
         } elseif ('2.0' == $packageSchemaVersion || '2.1' == $packageSchemaVersion) {
             $children = $package->contents->children();
             $packageName = (string) $package->name;
             $packageVersion = (string) $package->version->release;
             $sourceDir = $packageName . '-' . $packageVersion;
-            $result = $this->buildSourceList20($children, $role, $sourceDir);
+            $result = $this->buildSourceList20($children, $roles, $sourceDir);
+
+            $namespaces = $package->getNamespaces();
+            $package->registerXPathNamespace('ns', $namespaces['']);
+            $releaseNodes = $package->xpath('ns:phprelease');
+            $this->applyRelease($result, $releaseNodes, $vars);
         } else {
             throw new \RuntimeException('Unsupported schema version of package definition file.');
         }
@@ -134,7 +160,34 @@ class PearPackageExtractor
         return $result;
     }
 
-    private function buildSourceList10($children, $targetRole, $source = '', $target = '', $role = null)
+    private function applyRelease(&$actions, $releaseNodes, $vars)
+    {
+        foreach ($releaseNodes as $releaseNode) {
+            $requiredOs = (string) ($releaseNode->installconditions && $releaseNode->installconditions->os && $releaseNode->installconditions->os->name) ?: '';
+            if ($requiredOs && $vars['os'] != $requiredOs)
+                continue;
+
+            if ($releaseNode->filelist) {
+                foreach ($releaseNode->filelist->children() as $action) {
+                    if ('install' == $action->getName()) {
+                        $name = (string) $action['name'];
+                        $as = (string) $action['as'];
+                        if (isset($actions[$name])) {
+                            $actions[$name]['to'] = $as;
+                        }
+                    } elseif ('ignore' == $action->getName()) {
+                        $name = (string) $action['name'];
+                        unset($actions[$name]);
+                    } else {
+                        // unknown action
+                    }
+                }
+            }
+            break;
+        }
+    }
+
+    private function buildSourceList10($children, $targetRoles, $source = '', $target = '', $role = null)
     {
         $result = array();
 
@@ -145,14 +198,15 @@ class PearPackageExtractor
                 $dirSource = $this->combine($source, (string) $child['name']);
                 $dirTarget = $child['baseinstalldir'] ? : $target;
                 $dirRole = $child['role'] ? : $role;
-                $dirFiles = $this->buildSourceList10($child->children(), $targetRole, $dirSource, $dirTarget, $dirRole);
+                $dirFiles = $this->buildSourceList10($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole);
                 $result = array_merge($result, $dirFiles);
             } elseif ($child->getName() == 'file') {
-                if (($child['role'] ? : $role) == $targetRole) {
+                $fileRole = (string) $child['role'] ? : $role;
+                if (isset($targetRoles[$fileRole])) {
                     $fileName = (string) ($child['name'] ? : $child[0]); // $child[0] means text content
                     $fileSource = $this->combine($source, $fileName);
                     $fileTarget = $this->combine((string) $child['baseinstalldir'] ? : $target, $fileName);
-                    $result[] = array('from' => $fileSource, 'to' => $fileTarget);
+                    $result[(string) $child['name']] = array('from' => $fileSource, 'to' => $fileTarget, 'role' => $fileRole, 'tasks' => array());
                 }
             }
         }
@@ -160,24 +214,31 @@ class PearPackageExtractor
         return $result;
     }
 
-    private function buildSourceList20($children, $targetRole, $source = '', $target = '', $role = null)
+    private function buildSourceList20($children, $targetRoles, $source = '', $target = '', $role = null)
     {
         $result = array();
 
         // enumerating files
         foreach ($children as $child) {
             /** @var $child \SimpleXMLElement */
-            if ($child->getName() == 'dir') {
+            if ('dir' == $child->getName()) {
                 $dirSource = $this->combine($source, $child['name']);
                 $dirTarget = $child['baseinstalldir'] ? : $target;
                 $dirRole = $child['role'] ? : $role;
-                $dirFiles = $this->buildSourceList20($child->children(), $targetRole, $dirSource, $dirTarget, $dirRole);
+                $dirFiles = $this->buildSourceList20($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole);
                 $result = array_merge($result, $dirFiles);
-            } elseif ($child->getName() == 'file') {
-                if (($child['role'] ? : $role) == $targetRole) {
+            } elseif ('file' == $child->getName()) {
+                $fileRole = (string) $child['role'] ? : $role;
+                if (isset($targetRoles[$fileRole])) {
                     $fileSource = $this->combine($source, (string) $child['name']);
                     $fileTarget = $this->combine((string) ($child['baseinstalldir'] ? : $target), (string) $child['name']);
-                    $result[] = array('from' => $fileSource, 'to' => $fileTarget);
+                    $fileTasks = array();
+                    foreach ($child->children('http://pear.php.net/dtd/tasks-1.0') as $taskNode) {
+                        if ('replace' == $taskNode->getName()) {
+                            $fileTasks[] = array('from' => (string) $taskNode->attributes()->from, 'to' => (string) $taskNode->attributes()->to);
+                        }
+                    }
+                    $result[(string) $child['name']] = array('from' => $fileSource, 'to' => $fileTarget, 'role' => $fileRole, 'tasks' => $fileTasks);
                 }
             }
         }

+ 1 - 1
src/Composer/Factory.php

@@ -225,7 +225,6 @@ class Factory
         $dm->setDownloader('git', new Downloader\GitDownloader($io));
         $dm->setDownloader('svn', new Downloader\SvnDownloader($io));
         $dm->setDownloader('hg', new Downloader\HgDownloader($io));
-        $dm->setDownloader('pear', new Downloader\PearDownloader($io));
         $dm->setDownloader('zip', new Downloader\ZipDownloader($io));
         $dm->setDownloader('tar', new Downloader\TarDownloader($io));
         $dm->setDownloader('phar', new Downloader\PharDownloader($io));
@@ -251,6 +250,7 @@ class Factory
     protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io)
     {
         $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null));
+        $im->addInstaller(new Installer\PearInstaller($io, $composer, 'pear-library'));
         $im->addInstaller(new Installer\InstallerInstaller($io, $composer));
         $im->addInstaller(new Installer\MetapackageInstaller($io));
     }

+ 32 - 7
src/Composer/Installer/LibraryInstaller.php

@@ -82,7 +82,7 @@ class LibraryInstaller implements InstallerInterface
             $this->removeBinaries($package);
         }
 
-        $this->downloadManager->download($package, $downloadPath);
+        $this->installCode($package);
         $this->installBinaries($package);
         if (!$repo->hasPackage($package)) {
             $repo->addPackage(clone $package);
@@ -102,7 +102,7 @@ class LibraryInstaller implements InstallerInterface
         $downloadPath = $this->getInstallPath($initial);
 
         $this->removeBinaries($initial);
-        $this->downloadManager->update($initial, $target, $downloadPath);
+        $this->updateCode($initial, $target, $downloadPath);
         $this->installBinaries($target);
         $repo->removePackage($initial);
         if (!$repo->hasPackage($target)) {
@@ -123,7 +123,7 @@ class LibraryInstaller implements InstallerInterface
 
         $downloadPath = $this->getInstallPath($package);
 
-        $this->downloadManager->remove($package, $downloadPath);
+        $this->removeCode($package);
         $this->removeBinaries($package);
         $repo->removePackage($package);
 
@@ -146,12 +146,36 @@ class LibraryInstaller implements InstallerInterface
         return ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getPrettyName() . ($targetDir ? '/'.$targetDir : '');
     }
 
+    protected function installCode(PackageInterface $package)
+    {
+        $downloadPath = $this->getInstallPath($package);
+        $this->downloadManager->download($package, $downloadPath);
+    }
+
+    protected function updateCode(PackageInterface $initial, PackageInterface $target)
+    {
+        $downloadPath = $this->getInstallPath($initial);
+        $this->downloadManager->update($initial, $target, $downloadPath);
+    }
+
+    protected function removeCode(PackageInterface $package)
+    {
+        $downloadPath = $this->getInstallPath($package);
+        $this->downloadManager->remove($package, $downloadPath);
+    }
+
+    protected function getBinaries(PackageInterface $package)
+    {
+        return $package->getBinaries();
+    }
+
     protected function installBinaries(PackageInterface $package)
     {
-        if (!$package->getBinaries()) {
+        $binaries = $this->getBinaries($package);
+        if (!$binaries) {
             return;
         }
-        foreach ($package->getBinaries() as $bin) {
+        foreach ($binaries as $bin) {
             $this->initializeBinDir();
             $link = $this->binDir.'/'.basename($bin);
             if (file_exists($link)) {
@@ -193,10 +217,11 @@ class LibraryInstaller implements InstallerInterface
 
     protected function removeBinaries(PackageInterface $package)
     {
-        if (!$package->getBinaries()) {
+        $binaries = $this->getBinaries($package);
+        if (!$binaries) {
             return;
         }
-        foreach ($package->getBinaries() as $bin) {
+        foreach ($binaries as $bin) {
             $link = $this->binDir.'/'.basename($bin);
             if (!file_exists($link)) {
                 continue;

+ 95 - 0
src/Composer/Installer/PearInstaller.php

@@ -0,0 +1,95 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Installer;
+
+use Composer\IO\IOInterface;
+use Composer\Composer;
+use Composer\Downloader\PearPackageExtractor;
+use Composer\Downloader\DownloadManager;
+use Composer\Repository\InstalledRepositoryInterface;
+use Composer\Package\PackageInterface;
+use Composer\Util\Filesystem;
+
+/**
+ * Package installation manager.
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class PearInstaller extends LibraryInstaller
+{
+    private $filesystem;
+
+    /**
+     * Initializes library installer.
+     *
+     * @param string          $vendorDir relative path for packages home
+     * @param string          $binDir    relative path for binaries
+     * @param DownloadManager $dm        download manager
+     * @param IOInterface     $io        io instance
+     * @param string          $type      package type that this installer handles
+     */
+    public function __construct(IOInterface $io, Composer $composer, $type = 'pear-library')
+    {
+        $this->filesystem = new Filesystem();
+        parent::__construct($io, $composer, $type);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
+    {
+        $this->uninstall($repo, $initial);
+        $this->install($repo, $target);
+    }
+
+    protected function installCode(PackageInterface $package)
+    {
+        parent::installCode($package);
+
+        $isWindows = defined('PHP_WINDOWS_VERSION_BUILD') ? true : false;
+
+        $vars = array(
+            'os' => $isWindows ? 'windows' : 'linux',
+            'php_bin' => ($isWindows ? getenv('PHPRC') .'php.exe' : `which php`),
+            'pear_php' => $this->getInstallPath($package),
+            'bin_dir' => $this->getInstallPath($package) . '/bin',
+            'php_dir' => $this->getInstallPath($package),
+            'data_dir' => '@DATA_DIR@',
+            'version' => $package->getPrettyVersion(),
+        );
+
+        $packageArchive = $this->getInstallPath($package).'/'.pathinfo($package->getDistUrl(), PATHINFO_BASENAME);
+        $pearExtractor = new PearPackageExtractor($packageArchive);
+        $pearExtractor->extractTo($this->getInstallPath($package), array('php' => '/', 'script' => '/bin'), $vars);
+
+        if ($this->io->isVerbose()) {
+            $this->io->write('    Cleaning up');
+        }
+        unlink($packageArchive);
+    }
+
+    protected function getBinaries(PackageInterface $package)
+    {
+        $binariesPath = $this->getInstallPath($package) . '/bin/';
+        $binaries = array();
+        if (file_exists($binariesPath)) {
+            foreach (new \FilesystemIterator($binariesPath, \FilesystemIterator::KEY_AS_FILENAME) as $fileName => $value) {
+                $binaries[] = 'bin/'.$fileName;
+            }
+        }
+
+        return $binaries;
+    }
+}

+ 1 - 1
src/Composer/Repository/Pear/BaseChannelReader.php

@@ -73,7 +73,7 @@ abstract class BaseChannelReader
 
         if (false == $xml) {
             $url = rtrim($origin, '/') . '/' . ltrim($path, '/');
-            throw new \UnexpectedValueException('The PEAR channel at ' . $origin . ' is broken.');
+            throw new \UnexpectedValueException(sprintf('The PEAR channel at ' . $origin . ' is broken. (Invalid XML at file `%s`)', $path));
         }
 
         return $xml;

+ 0 - 2
src/Composer/Repository/Pear/ChannelReader.php

@@ -13,8 +13,6 @@
 namespace Composer\Repository\Pear;
 
 use Composer\Util\RemoteFilesystem;
-use Composer\Package\LinkConstraint\VersionConstraint;
-use Composer\Package\Link;
 
 /**
  * PEAR Channel package reader.

+ 1 - 0
src/Composer/Repository/Pear/PackageDependencyParser.php

@@ -33,6 +33,7 @@ class PackageDependencyParser
         if (!$this->isHash($depArray)) {
             return new DependencyInfo($this->buildDependency10Info($depArray), array());
         }
+
         return $this->buildDependency20Info($depArray);
     }
 

+ 4 - 3
src/Composer/Repository/PearRepository.php

@@ -71,6 +71,7 @@ class PearRepository extends ArrayRepository
             $channelInfo = $reader->read($this->url);
         } catch (\Exception $e) {
             $this->io->write('<warning>PEAR repository from '.$this->url.' could not be loaded. '.$e->getMessage().'</warning>');
+
             return;
         }
         $packages = $this->buildComposerPackages($channelInfo, $this->versionParser);
@@ -146,9 +147,9 @@ class PearRepository extends ArrayRepository
                 }
 
                 $package = new MemoryPackage($composerPackageName, $normalizedVersion, $version);
-                $package->setType('library');
+                $package->setType('pear-library');
                 $package->setDescription($packageDefinition->getDescription());
-                $package->setDistType('pear');
+                $package->setDistType('file');
                 $package->setDistUrl($distUrl);
                 $package->setAutoload(array('classmap' => array('')));
                 $package->setIncludePaths(array('/'));
@@ -178,7 +179,7 @@ class PearRepository extends ArrayRepository
     /**
      * Softened version parser.
      *
-     * @param string $version
+     * @param  string      $version
      * @return null|string
      */
     private function parseVersion($version)

+ 11 - 1
tests/Composer/Test/Downloader/Fixtures/Package_v2.1/package.xml

@@ -19,7 +19,17 @@
         <dir name="/">
             <file role="php" name="php/Zend/Authentication/Storage/StorageInterface.php"/>
             <file role="php" name="php/Zend/Authentication/Result.php"/>
+            <file role="script" name="php/Test.php">
+                <tasks:replace from='@version@' to='version' />
+            </file>
+            <file role="php" name="renamedFile.php"/>
+            <file role="php" name="ignoredFile.php"/>
         </dir>
     </contents>
-    <phprelease/>
+    <phprelease>
+        <filelist>
+            <install name='renamedFile.php' as='correctFile.php' />
+            <ignore name='ignoredFile.php' />
+        </filelist>
+    </phprelease>
 </package>

+ 0 - 38
tests/Composer/Test/Downloader/PearDownloaderTest.php

@@ -1,38 +0,0 @@
-<?php
-
-/*
- * This file is part of Composer.
- *
- * (c) Nils Adermann <naderman@naderman.de>
- *     Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Composer\Test\Downloader;
-
-use Composer\Downloader\PearDownloader;
-
-class PearDownloaderTest extends \PHPUnit_Framework_TestCase
-{
-    public function testErrorMessages()
-    {
-        $packageMock = $this->getMock('Composer\Package\PackageInterface');
-        $packageMock->expects($this->any())
-            ->method('getDistUrl')
-            ->will($this->returnValue('file://'.__FILE__))
-        ;
-
-        $io = $this->getMock('Composer\IO\IOInterface');
-        $downloader = new PearDownloader($io);
-
-        try {
-            $downloader->download($packageMock, sys_get_temp_dir().'/composer-pear-test');
-            $this->fail('Download of invalid pear packages should throw an exception');
-        } catch (\UnexpectedValueException $e) {
-            $this->assertContains('Failed to extract PEAR package', $e->getMessage());
-        }
-    }
-
-}

+ 62 - 10
tests/Composer/Test/Downloader/PearPackageExtractorTest.php

@@ -22,20 +22,26 @@ class PearPackageExtractorTest extends \PHPUnit_Framework_TestCase
         $method = new \ReflectionMethod($extractor, 'buildCopyActions');
         $method->setAccessible(true);
 
-        $fileActions = $method->invoke($extractor, __DIR__ . '/Fixtures/Package_v1.0', 'php');
+        $fileActions = $method->invoke($extractor, __DIR__ . '/Fixtures/Package_v1.0', array('php' => '/'), array());
 
         $expectedFileActions = array(
-            0 => Array(
+            'Gtk.php' => Array(
                 'from' => 'PEAR_Frontend_Gtk-0.4.0/Gtk.php',
                 'to' => 'PEAR/Frontend/Gtk.php',
+                'role' => 'php',
+                'tasks' => array(),
             ),
-            1 => Array(
+            'Gtk/Config.php' => Array(
                 'from' => 'PEAR_Frontend_Gtk-0.4.0/Gtk/Config.php',
                 'to' => 'PEAR/Frontend/Gtk/Config.php',
+                'role' => 'php',
+                'tasks' => array(),
             ),
-            2 => Array(
+            'Gtk/xpm/black_close_icon.xpm' => Array(
                 'from' => 'PEAR_Frontend_Gtk-0.4.0/Gtk/xpm/black_close_icon.xpm',
                 'to' => 'PEAR/Frontend/Gtk/xpm/black_close_icon.xpm',
+                'role' => 'php',
+                'tasks' => array(),
             )
         );
         $this->assertSame($expectedFileActions, $fileActions);
@@ -47,12 +53,14 @@ class PearPackageExtractorTest extends \PHPUnit_Framework_TestCase
         $method = new \ReflectionMethod($extractor, 'buildCopyActions');
         $method->setAccessible(true);
 
-        $fileActions = $method->invoke($extractor, __DIR__ . '/Fixtures/Package_v2.0', 'php');
+        $fileActions = $method->invoke($extractor, __DIR__ . '/Fixtures/Package_v2.0', array('php' => '/'), array());
 
         $expectedFileActions = array(
-            0 => Array(
+            'URL.php' => Array(
                 'from' => 'Net_URL-1.0.15/URL.php',
                 'to' => 'Net/URL.php',
+                'role' => 'php',
+                'tasks' => array(),
             )
         );
         $this->assertSame($expectedFileActions, $fileActions);
@@ -64,18 +72,62 @@ class PearPackageExtractorTest extends \PHPUnit_Framework_TestCase
         $method = new \ReflectionMethod($extractor, 'buildCopyActions');
         $method->setAccessible(true);
 
-        $fileActions = $method->invoke($extractor, __DIR__ . '/Fixtures/Package_v2.1', 'php');
+        $fileActions = $method->invoke($extractor, __DIR__ . '/Fixtures/Package_v2.1', array('php' => '/', 'script' => '/bin'), array());
 
         $expectedFileActions = array(
-            0 => Array(
+            'php/Zend/Authentication/Storage/StorageInterface.php' => Array(
                 'from' => 'Zend_Authentication-2.0.0beta4/php/Zend/Authentication/Storage/StorageInterface.php',
                 'to' => '/php/Zend/Authentication/Storage/StorageInterface.php',
+                'role' => 'php',
+                'tasks' => array(),
             ),
-            1 => Array(
+            'php/Zend/Authentication/Result.php' => Array(
                 'from' => 'Zend_Authentication-2.0.0beta4/php/Zend/Authentication/Result.php',
                 'to' => '/php/Zend/Authentication/Result.php',
-            )
+                'role' => 'php',
+                'tasks' => array(),
+            ),
+            'php/Test.php' => array (
+                'from' => 'Zend_Authentication-2.0.0beta4/php/Test.php',
+                'to' => '/php/Test.php',
+                'role' => 'script',
+                'tasks' => array (
+                    array (
+                        'from' => '@version@',
+                        'to' => 'version',
+                    )
+                )
+            ),
+            'renamedFile.php' => Array(
+                'from' => 'Zend_Authentication-2.0.0beta4/renamedFile.php',
+                'to' => 'correctFile.php',
+                'role' => 'php',
+                'tasks' => array(),
+            ),
         );
         $this->assertSame($expectedFileActions, $fileActions);
     }
+
+    public function testShouldPerformReplacements()
+    {
+        $from = tempnam(sys_get_temp_dir(), 'pear-extract');
+        $to = $from.'-to';
+
+        $original = 'replaced: @placeholder@; not replaced: @another@; replaced again: @placeholder@';
+        $expected = 'replaced: value; not replaced: @another@; replaced again: value';
+
+        file_put_contents($from, $original);
+
+        $extractor = new PearPackageExtractor($from);
+        $method = new \ReflectionMethod($extractor, 'copyFile');
+        $method->setAccessible(true);
+
+        $method->invoke($extractor, $from, $to, array(array('from' => '@placeholder@', 'to' => 'variable')), array('variable' => 'value'));
+        $result = file_get_contents($to);
+
+        unlink($to);
+        unlink($from);
+
+        $this->assertEquals($expected, $result);
+    }
 }

+ 3 - 3
tests/Composer/Test/Installer/LibraryInstallerTest.php

@@ -106,7 +106,7 @@ class LibraryInstallerTest extends TestCase
         $package = $this->createPackageMock();
 
         $package
-            ->expects($this->once())
+            ->expects($this->any())
             ->method('getPrettyName')
             ->will($this->returnValue('some/package'));
 
@@ -136,7 +136,7 @@ class LibraryInstallerTest extends TestCase
         $target  = $this->createPackageMock();
 
         $initial
-            ->expects($this->once())
+            ->expects($this->any())
             ->method('getPrettyName')
             ->will($this->returnValue('package1'));
 
@@ -175,7 +175,7 @@ class LibraryInstallerTest extends TestCase
         $package = $this->createPackageMock();
 
         $package
-            ->expects($this->once())
+            ->expects($this->any())
             ->method('getPrettyName')
             ->will($this->returnValue('pkg'));
 

+ 2 - 2
tests/Composer/Test/Package/LockerTest.php

@@ -138,7 +138,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue('pkg1'));
         $package1
             ->expects($this->once())
-            ->method('getPrettyVersion')
+            ->method('getVersion')
             ->will($this->returnValue('1.0.0-beta'));
 
         $package2
@@ -147,7 +147,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue('pkg2'));
         $package2
             ->expects($this->once())
-            ->method('getPrettyVersion')
+            ->method('getVersion')
             ->will($this->returnValue('0.1.10'));
 
         $json

+ 2 - 2
tests/Composer/Test/Repository/Pear/ChannelReaderTest.php

@@ -118,8 +118,8 @@ class ChannelReaderTest extends TestCase
         $packages = $ref->invoke($reader, $channelInfo, new VersionParser());
 
         $expectedPackage = new MemoryPackage('pear-test.loc/sample', '1.0.0.1' , '1.0.0.1');
-        $expectedPackage->setType('library');
-        $expectedPackage->setDistType('pear');
+        $expectedPackage->setType('pear-library');
+        $expectedPackage->setDistType('file');
         $expectedPackage->setDescription('description');
         $expectedPackage->setDistUrl("http://test.loc/get/sample-1.0.0.1.tgz");
         $expectedPackage->setAutoload(array('classmap' => array('')));

+ 2 - 0
tests/bootstrap.php

@@ -10,5 +10,7 @@
  * file that was distributed with this source code.
  */
 
+error_reporting(E_ALL);
+
 $loader = require __DIR__.'/../src/bootstrap.php';
 $loader->add('Composer\Test', __DIR__);