浏览代码

Merge remote-tracking branch 'vuhl/master'

Conflicts:
	src/Composer/Factory.php
Jordi Boggiano 12 年之前
父节点
当前提交
9a806658d6

+ 96 - 0
src/Composer/Downloader/PerforceDownloader.php

@@ -0,0 +1,96 @@
+<?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;
+use Composer\Repository\VcsRepository;
+use Composer\Util\Perforce;
+
+/**
+ * @author Matt Whittom <Matt.Whittom@veteransunited.com>
+ */
+class PerforceDownloader extends VcsDownloader
+{
+    protected $perforce;
+    protected $perforceInjected = false;
+
+    /**
+     * {@inheritDoc}
+     */
+    public function doDownload(PackageInterface $package, $path)
+    {
+        $ref = $package->getSourceReference();
+        $label = $package->getPrettyVersion();
+
+        $this->io->write('    Cloning ' . $ref);
+        $this->initPerforce($package, $path, $ref);
+        $this->perforce->setStream($ref);
+        $this->perforce->p4Login($this->io);
+        $this->perforce->writeP4ClientSpec();
+        $this->perforce->connectClient();
+        $this->perforce->syncCodeBase($label);
+        $this->perforce->cleanupClientSpec();
+    }
+
+    private function initPerforce($package, $path, $ref)
+    {
+        if ($this->perforceInjected) {
+            return;
+        }
+        $repository = $package->getRepository();
+        $repoConfig = null;
+        if ($repository instanceof VcsRepository) {
+            $repoConfig = $this->getRepoConfig($repository);
+        }
+        $this->perforce = Perforce::createPerforce($repoConfig, $package->getSourceUrl(), $path);
+    }
+
+    private function getRepoConfig(VcsRepository $repository)
+    {
+        return $repository->getRepoConfig();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function doUpdate(PackageInterface $initial, PackageInterface $target, $path)
+    {
+        $this->doDownload($target, $path);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getLocalChanges(PackageInterface $package, $path)
+    {
+        $this->io->write('Perforce driver does not check for local changes before overriding', true);
+        return;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function getCommitLogs($fromReference, $toReference, $path)
+    {
+        $commitLogs = $this->perforce->getCommitLogs($fromReference, $toReference);
+        return $commitLogs;
+    }
+
+    public function injectPerforce($perforce)
+    {
+        $this->perforce = $perforce;
+        $this->perforceInjected = true;
+    }
+}

+ 1 - 0
src/Composer/Factory.php

@@ -355,6 +355,7 @@ class Factory
         $dm->setDownloader('git', new Downloader\GitDownloader($io, $config));
         $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config));
         $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config));
+        $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config));
         $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache));
         $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $eventDispatcher, $cache));
         $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache));

+ 190 - 0
src/Composer/Repository/Vcs/PerforceDriver.php

@@ -0,0 +1,190 @@
+<?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\Repository\Vcs;
+
+use Composer\IO\IOInterface;
+use Composer\Util\ProcessExecutor;
+use Composer\Util\Filesystem;
+use Composer\Util\Perforce;
+
+/**
+ * @author Matt Whittom <Matt.Whittom@veteransunited.com>
+ */
+class PerforceDriver extends VcsDriver
+{
+    protected $depot;
+    protected $branch;
+    protected $perforce;
+    protected $composerInfo;
+    protected $composerInfoIdentifier;
+
+    /**
+     * {@inheritDoc}
+     */
+    public function initialize()
+    {
+        $this->depot = $this->repoConfig['depot'];
+        $this->branch = '';
+        if (isset($this->repoConfig['branch'])) {
+            $this->branch = $this->repoConfig['branch'];
+        }
+
+        $this->initPerforce($this->repoConfig);
+        $this->perforce->p4Login($this->io);
+        $this->perforce->checkStream($this->depot);
+
+        $this->perforce->writeP4ClientSpec();
+        $this->perforce->connectClient();
+
+        return true;
+    }
+
+    private function initPerforce($repoConfig)
+    {
+        if (isset($this->perforce)) {
+            return;
+        }
+
+        $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot;
+        $this->perforce = Perforce::createPerforce($repoConfig, $this->getUrl(), $repoDir, $this->process);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getComposerInformation($identifier)
+    {
+        if (isset($this->composerInfoIdentifier)) {
+            if (strcmp($identifier, $this->composerInfoIdentifier) === 0) {
+                return $this->composerInfo;
+            }
+        }
+        $composer_info = $this->perforce->getComposerInformation($identifier);
+
+        return $composer_info;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getRootIdentifier()
+    {
+        return $this->branch;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getBranches()
+    {
+        $branches = $this->perforce->getBranches();
+
+        return $branches;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getTags()
+    {
+        $tags = $this->perforce->getTags();
+
+        return $tags;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getDist($identifier)
+    {
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getSource($identifier)
+    {
+        $source = array(
+            'type'      => 'perforce',
+            'url'       => $this->repoConfig['url'],
+            'reference' => $identifier,
+            'p4user'    => $this->perforce->getUser()
+        );
+
+        return $source;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getUrl()
+    {
+        return $this->url;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function hasComposerFile($identifier)
+    {
+        $this->composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier);
+        $this->composerInfoIdentifier = $identifier;
+        $result = false;
+        if (isset($this->composerInfo)) {
+            $result = count($this->composerInfo) > 0;
+        }
+        return $result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getContents($url)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public static function supports(IOInterface $io, $url, $deep = false)
+    {
+        return Perforce::checkServerExists($url, new ProcessExecutor);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function cleanup()
+    {
+        $this->perforce->cleanupClientSpec();
+        $this->perforce = null;
+    }
+
+    public function getDepot()
+    {
+        return $this->depot;
+    }
+
+    public function getBranch()
+    {
+        return $this->branch;
+    }
+
+    public function injectPerforce(Perforce $perforce)
+    {
+        $this->perforce = $perforce;
+    }
+}

+ 8 - 0
src/Composer/Repository/Vcs/VcsDriver.php

@@ -110,4 +110,12 @@ abstract class VcsDriver implements VcsDriverInterface
     {
         return (bool) preg_match('{^(file://|/|[a-z]:[\\\\/])}i', $url);
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function cleanup()
+    {
+        return;
+    }
 }

+ 6 - 0
src/Composer/Repository/Vcs/VcsDriverInterface.php

@@ -81,6 +81,12 @@ interface VcsDriverInterface
      */
     public function hasComposerFile($identifier);
 
+    /**
+     * Performs any cleanup necessary as the driver is not longer needed
+     *
+     */
+    public function cleanup();
+
     /**
      * Checks if this driver can handle a given url
      *

+ 7 - 0
src/Composer/Repository/VcsRepository.php

@@ -46,6 +46,7 @@ class VcsRepository extends ArrayRepository
             'git'           => 'Composer\Repository\Vcs\GitDriver',
             'hg-bitbucket'  => 'Composer\Repository\Vcs\HgBitbucketDriver',
             'hg'            => 'Composer\Repository\Vcs\HgDriver',
+            'perforce'      => 'Composer\Repository\Vcs\PerforceDriver',
             // svn must be last because identifying a subversion server for sure is practically impossible
             'svn'           => 'Composer\Repository\Vcs\SvnDriver',
         );
@@ -58,6 +59,11 @@ class VcsRepository extends ArrayRepository
         $this->repoConfig = $repoConfig;
     }
 
+    public function getRepoConfig()
+    {
+        return $this->repoConfig;
+    }
+
     public function setLoader(LoaderInterface $loader)
     {
         $this->loader = $loader;
@@ -247,6 +253,7 @@ class VcsRepository extends ArrayRepository
                 continue;
             }
         }
+        $driver->cleanup();
 
         if (!$verbose) {
             $this->io->overwrite('', false);

+ 518 - 0
src/Composer/Util/Perforce.php

@@ -0,0 +1,518 @@
+<?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\Util;
+
+use Composer\IO\IOInterface;
+use Symfony\Component\Process\Process;
+
+/**
+ * @author Matt Whittom <Matt.Whittom@veteransunited.com>
+ */
+class Perforce
+{
+
+    protected $path;
+    protected $p4Depot;
+    protected $p4Client;
+    protected $p4User;
+    protected $p4Password;
+    protected $p4Port;
+    protected $p4Stream;
+    protected $p4ClientSpec;
+    protected $p4DepotType;
+    protected $p4Branch;
+    protected $process;
+    protected $uniquePerforceClientName;
+    protected $windowsFlag;
+
+
+    public static function createPerforce($repoConfig, $port, $path, ProcessExecutor $process = null)
+    {
+        if (!isset($process)) {
+            $process = new ProcessExecutor;
+        }
+        $isWindows = defined('PHP_WINDOWS_VERSION_BUILD');
+
+        $perforce = new Perforce($repoConfig, $port, $path, $process, $isWindows);
+        return $perforce;
+    }
+
+    public function __construct($repoConfig, $port, $path, ProcessExecutor $process, $isWindows)
+    {
+        $this->windowsFlag = $isWindows;
+        $this->p4Port = $port;
+        $this->path = $path;
+        $fs = new Filesystem();
+        $fs->ensureDirectoryExists($path);
+        $this->process = $process;
+        $this->initialize($repoConfig);
+    }
+
+    public function initialize($repoConfig)
+    {
+        $this->uniquePerforceClientName = $this->generateUniquePerforceClientName();
+        if (null == $repoConfig) {
+            return;
+        }
+        if (isset($repoConfig['unique_perforce_client_name'])) {
+            $this->uniquePerforceClientName = $repoConfig['unique_perforce_client_name'];
+        }
+
+        if (isset($repoConfig['depot'])) {
+            $this->p4Depot = $repoConfig['depot'];
+        }
+        if (isset($repoConfig['branch'])) {
+            $this->p4Branch = $repoConfig['branch'];
+        }
+        if (isset($repoConfig['p4user'])) {
+            $this->p4User = $repoConfig['p4user'];
+        } else {
+            $this->p4User = $this->getP4variable('P4USER');
+        }
+        if (isset($repoConfig['p4password'])) {
+            $this->p4Password = $repoConfig['p4password'];
+        }
+    }
+
+    public function initializeDepotAndBranch($depot, $branch)
+    {
+        if (isset($depot)) {
+            $this->p4Depot = $depot;
+        }
+        if (isset($branch)) {
+            $this->p4Branch = $branch;
+        }
+    }
+
+    public function generateUniquePerforceClientName()
+    {
+        return gethostname() . "_" . time();
+    }
+
+    public function cleanupClientSpec()
+    {
+        $client = $this->getClient();
+        $command = 'p4 client -d $client';
+        $this->executeCommand($command);
+        $clientSpec = $this->getP4ClientSpec();
+        $fileSystem = new FileSystem($this->process);
+        $fileSystem->remove($clientSpec);
+    }
+
+    protected function executeCommand($command)
+    {
+        $result = "";
+        $this->process->execute($command, $result);
+
+        return $result;
+    }
+
+    public function getClient()
+    {
+        if (!isset($this->p4Client)) {
+            $cleanStreamName = str_replace('@', '', str_replace('/', '_', str_replace('//', '', $this->getStream())));
+            $this->p4Client = 'composer_perforce_' . $this->uniquePerforceClientName . '_' . $cleanStreamName;
+        }
+
+        return $this->p4Client;
+    }
+
+    protected function getPath()
+    {
+        return $this->path;
+    }
+
+    protected function getPort()
+    {
+        return $this->p4Port;
+    }
+
+    public function setStream($stream)
+    {
+        $this->p4Stream = $stream;
+        $index = strrpos($stream, '/');
+        //Stream format is //depot/stream, while non-streaming depot is //depot
+        if ($index > 2) {
+            $this->p4DepotType = 'stream';
+        }
+    }
+
+    public function isStream()
+    {
+        return (strcmp($this->p4DepotType, 'stream') === 0);
+    }
+
+    public function getStream()
+    {
+        if (!isset($this->p4Stream)) {
+            if ($this->isStream()) {
+                $this->p4Stream = '//' . $this->p4Depot . '/' . $this->p4Branch;
+            } else {
+                $this->p4Stream = '//' . $this->p4Depot;
+            }
+        }
+        return $this->p4Stream;
+    }
+
+    public function getStreamWithoutLabel($stream)
+    {
+        $index = strpos($stream, '@');
+        if ($index === false) {
+            return $stream;
+        }
+
+        return substr($stream, 0, $index);
+    }
+
+    public function getP4ClientSpec()
+    {
+        $p4clientSpec = $this->path . '/' . $this->getClient() . '.p4.spec';
+
+        return $p4clientSpec;
+    }
+
+    public function getUser()
+    {
+        return $this->p4User;
+    }
+
+    public function queryP4User(IOInterface $io)
+    {
+        $this->getUser();
+        if (strlen($this->p4User) > 0) {
+            return;
+        }
+        $this->p4User = $this->getP4variable('P4USER');
+        if (strlen($this->p4User) > 0) {
+            return;
+        }
+        $this->p4User = $io->ask('Enter P4 User:');
+        if ($this->windowsFlag) {
+            $command = 'p4 set P4USER=' . $this->p4User;
+        } else {
+            $command = 'export P4USER=' . $this->p4User;
+        }
+        $result = $this->executeCommand($command);
+    }
+
+    protected function getP4variable($name)
+    {
+        if ($this->windowsFlag) {
+            $command = 'p4 set';
+            $result = $this->executeCommand($command);
+            $resArray = explode(PHP_EOL, $result);
+            foreach ($resArray as $line) {
+                $fields = explode('=', $line);
+                if (strcmp($name, $fields[0]) == 0) {
+                    $index = strpos($fields[1], ' ');
+                    if ($index === false) {
+                        $value = $fields[1];
+                    } else {
+                        $value = substr($fields[1], 0, $index);
+                    }
+                    $value = trim($value);
+
+                    return $value;
+                }
+            }
+        } else {
+            $command = 'echo $' . $name;
+            $result = trim($this->executeCommand($command));
+
+            return $result;
+        }
+    }
+
+    public function queryP4Password(IOInterface $io)
+    {
+        if (isset($this->p4Password)) {
+            return $this->p4Password;
+        }
+        $password = $this->getP4variable('P4PASSWD');
+        if (strlen($password) <= 0) {
+            $password = $io->askAndHideAnswer('Enter password for Perforce user ' . $this->getUser() . ': ');
+        }
+        $this->p4Password = $password;
+
+        return $password;
+    }
+
+    public function generateP4Command($command, $useClient = true)
+    {
+        $p4Command = 'p4 ';
+        $p4Command = $p4Command . '-u ' . $this->getUser() . ' ';
+        if ($useClient) {
+            $p4Command = $p4Command . '-c ' . $this->getClient() . ' ';
+        }
+        $p4Command = $p4Command . '-p ' . $this->getPort() . ' ';
+        $p4Command = $p4Command . $command;
+
+        return $p4Command;
+    }
+
+    public function isLoggedIn()
+    {
+        $command = $this->generateP4Command('login -s', false);
+        $result = trim($this->executeCommand($command));
+        $index = strpos($result, $this->getUser());
+        if ($index === false) {
+            return false;
+        }
+        return true;
+    }
+
+    public function connectClient()
+    {
+        $p4CreateClientCommand = $this->generateP4Command('client -i < ' . $this->getP4ClientSpec());
+        $this->executeCommand($p4CreateClientCommand);
+    }
+
+    public function syncCodeBase($label)
+    {
+        $prevDir = getcwd();
+        chdir($this->path);
+
+        $p4SyncCommand = $this->generateP4Command('sync -f ');
+        if (isset($label)) {
+            if (strcmp($label, 'dev-master') != 0) {
+                $p4SyncCommand = $p4SyncCommand . '@' . $label;
+            }
+        }
+        $result = $this->executeCommand($p4SyncCommand);
+
+        chdir($prevDir);
+    }
+
+    public function writeClientSpecToFile($spec)
+    {
+        fwrite($spec, 'Client: ' . $this->getClient() . PHP_EOL . PHP_EOL);
+        fwrite($spec, 'Update: ' . date('Y/m/d H:i:s') . PHP_EOL . PHP_EOL);
+        fwrite($spec, 'Access: ' . date('Y/m/d H:i:s') . PHP_EOL);
+        fwrite($spec, 'Owner:  ' . $this->getUser() . PHP_EOL . PHP_EOL);
+        fwrite($spec, 'Description:' . PHP_EOL);
+        fwrite($spec, '  Created by ' . $this->getUser() . ' from composer.' . PHP_EOL . PHP_EOL);
+        fwrite($spec, 'Root: ' . $this->getPath() . PHP_EOL . PHP_EOL);
+        fwrite($spec, 'Options:  noallwrite noclobber nocompress unlocked modtime rmdir' . PHP_EOL . PHP_EOL);
+        fwrite($spec, 'SubmitOptions:  revertunchanged' . PHP_EOL . PHP_EOL);
+        fwrite($spec, 'LineEnd:  local' . PHP_EOL . PHP_EOL);
+        if ($this->isStream()) {
+            fwrite($spec, 'Stream:' . PHP_EOL);
+            fwrite($spec, '  ' . $this->getStreamWithoutLabel($this->p4Stream) . PHP_EOL);
+        } else {
+            fwrite(
+                $spec,
+                'View:  ' . $this->getStream() . '/...  //' . $this->getClient() . '/... ' . PHP_EOL
+            );
+        }
+    }
+
+    public function writeP4ClientSpec()
+    {
+        $clientSpec = $this->getP4ClientSpec();
+        $spec = fopen($clientSpec, 'w');
+        try {
+            $this->writeClientSpecToFile($spec);
+        } catch (\Exception $e) {
+            fclose($spec);
+            throw $e;
+        }
+        fclose($spec);
+    }
+
+
+    protected function read($pipe, $name)
+    {
+        if (feof($pipe)) {
+            return;
+        }
+        $line = fgets($pipe);
+        while ($line != false) {
+            $line = fgets($pipe);
+        }
+
+        return;
+    }
+
+    public function windowsLogin($password)
+    {
+        $command = $this->generateP4Command(' login -a');
+        $process = new Process($command, null, null, $password);
+        return $process->run();
+    }
+
+
+    public function p4Login(IOInterface $io)
+    {
+        $this->queryP4User($io);
+        if (!$this->isLoggedIn()) {
+            $password = $this->queryP4Password($io);
+            if ($this->windowsFlag) {
+                $this->windowsLogin($password);
+            } else {
+                $command = 'echo ' . $password  . ' | ' . $this->generateP4Command(' login -a', false);
+                $this->executeCommand($command);
+            }
+        }
+    }
+
+    public static function checkServerExists($url, ProcessExecutor $processExecutor)
+    {
+        $result = '';
+        $processExecutor->execute('p4 -p ' . $url . ' info -s', $result);
+        return false === strpos($result, 'error');
+    }
+
+    public function getComposerInformation($identifier)
+    {
+        $index = strpos($identifier, '@');
+        if ($index === false) {
+            $composerJson = $identifier. '/composer.json';
+
+            return $this->getComposerInformationFromPath($composerJson);
+        }
+        return $this->getComposerInformationFromLabel($identifier, $index);
+    }
+
+    public function getComposerInformationFromPath($composerJson)
+    {
+        $command = $this->generateP4Command(' print ' . $composerJson);
+        $result = $this->executeCommand($command);
+        $index = strpos($result, '{');
+        if ($index === false) {
+            return '';
+        }
+        if ($index >= 0) {
+            $rawData = substr($result, $index);
+            $composer_info = json_decode($rawData, true);
+
+            return $composer_info;
+        }
+
+        return '';
+    }
+
+    public function getComposerInformationFromLabel($identifier, $index)
+    {
+        $composerJsonPath = substr($identifier, 0, $index) . '/composer.json' . substr($identifier, $index);
+        $command = $this->generateP4Command(' files ' . $composerJsonPath, false);
+        $result = $this->executeCommand($command);
+        $index2 = strpos($result, 'no such file(s).');
+        if ($index2 === false) {
+            $index3 = strpos($result, 'change');
+            if (!($index3 === false)) {
+                $phrase = trim(substr($result, $index3));
+                $fields = explode(' ', $phrase);
+                $id = $fields[1];
+                $composerJson = substr($identifier, 0, $index) . '/composer.json@' . $id;
+
+                return $this->getComposerInformationFromPath($composerJson);
+            }
+        }
+
+        return "";
+    }
+
+    public function getBranches()
+    {
+        $possibleBranches = array();
+        if (!$this->isStream()) {
+            $possibleBranches[$this->p4Branch] = $this->getStream();
+        } else {
+            $command = $this->generateP4Command('streams //' . $this->p4Depot . '/...');
+            $result = $this->executeCommand($command);
+            $resArray = explode(PHP_EOL, $result);
+            foreach ($resArray as $line) {
+                $resBits = explode(' ', $line);
+                if (count($resBits) > 4) {
+                    $branch = preg_replace('/[^A-Za-z0-9 ]/', '', $resBits[4]);
+                    $possibleBranches[$branch] = $resBits[1];
+                }
+            }
+        }
+        $branches = array();
+        $branches['master'] = $possibleBranches[$this->p4Branch];
+
+        return $branches;
+    }
+
+    public function getTags()
+    {
+        $command = $this->generateP4Command('labels');
+        $result = $this->executeCommand($command);
+        $resArray = explode(PHP_EOL, $result);
+        $tags = array();
+        foreach ($resArray as $line) {
+            $index = strpos($line, 'Label');
+            if (!($index === false)) {
+                $fields = explode(' ', $line);
+                $tags[$fields[1]] = $this->getStream() . '@' . $fields[1];
+            }
+        }
+
+        return $tags;
+    }
+
+    public function checkStream()
+    {
+        $command = $this->generateP4Command('depots', false);
+        $result = $this->executeCommand($command);
+        $resArray = explode(PHP_EOL, $result);
+        foreach ($resArray as $line) {
+            $index = strpos($line, 'Depot');
+            if (!($index === false)) {
+                $fields = explode(' ', $line);
+                if (strcmp($this->p4Depot, $fields[1]) === 0) {
+                    $this->p4DepotType = $fields[3];
+
+                    return $this->isStream();
+                }
+            }
+        }
+
+        return false;
+    }
+
+    protected function getChangeList($reference)
+    {
+        $index = strpos($reference, '@');
+        if ($index === false) {
+            return;
+        }
+        $label = substr($reference, $index);
+        $command = $this->generateP4Command(' changes -m1 ' . $label);
+        $changes = $this->executeCommand($command);
+        if (strpos($changes, 'Change') !== 0) {
+            return;
+        }
+        $fields = explode(' ', $changes);
+        $changeList = $fields[1];
+        return $changeList;
+    }
+
+    public function getCommitLogs($fromReference, $toReference)
+    {
+        $fromChangeList = $this->getChangeList($fromReference);
+        if ($fromChangeList == null) {
+            return;
+        }
+        $toChangeList = $this->getChangeList($toReference);
+        if ($toChangeList == null) {
+            return;
+        }
+        $index = strpos($fromReference, '@');
+        $main = substr($fromReference, 0, $index) . '/...';
+        $command = $this->generateP4Command('filelog ' . $main . '@' . $fromChangeList. ',' . $toChangeList);
+        $result = $this->executeCommand($command);
+        return $result;
+    }
+}

+ 108 - 0
tests/Composer/Test/Downloader/PerforceDownloaderTest.php

@@ -0,0 +1,108 @@
+<?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\PerforceDownloader;
+use Composer\Config;
+use Composer\Repository\VcsRepository;
+
+/**
+ * @author Matt Whittom <Matt.Whittom@veteransunited.com>
+ */
+class PerforceDownloaderTest extends \PHPUnit_Framework_TestCase
+{
+
+    private $io;
+    private $config;
+    private $testPath;
+    public static $repository;
+
+    protected function setUp()
+    {
+        $this->testPath = sys_get_temp_dir() . '/composer-test';
+        $this->config = new Config();
+        $this->config->merge(
+            array(
+                 'config' => array(
+                     'home' => $this->testPath,
+                 ),
+            )
+        );
+        $this->io = $this->getMock('Composer\IO\IOInterface');
+    }
+
+
+    public function testDoDownloadGetRepoConfig()
+    {
+        $downloader = new PerforceDownloader($this->io, $this->config);
+        $package = $this->getMock('Composer\Package\PackageInterface');
+        $repoConfig = array('url' => 'TEST_URL', 'p4user' => 'TEST_USER');
+        $repository = $this->getMock(
+            'Composer\Repository\VcsRepository',
+            array('getRepoConfig'),
+            array($repoConfig, $this->io, $this->config)
+        );
+        $package->expects($this->at(0))
+        ->method('getSourceReference')
+        ->will($this->returnValue('SOURCE_REF'));
+        $package->expects($this->at(1))
+        ->method('getPrettyVersion')
+        ->will($this->returnValue('100'));
+        $package->expects($this->at(2))
+        ->method('getRepository')
+        ->will($this->returnValue($repository));
+        $repository->expects($this->at(0))
+        ->method('getRepoConfig');
+        $path = $this->testPath;
+        $downloader->doDownload($package, $path);
+    }
+
+    public function testDoDownload()
+    {
+        $downloader = new PerforceDownloader($this->io, $this->config);
+        $repoConfig = array('depot' => 'TEST_DEPOT', 'branch' => 'TEST_BRANCH', 'p4user' => 'TEST_USER');
+        $port = 'TEST_PORT';
+        $path = 'TEST_PATH';
+        $process = $this->getmock('Composer\Util\ProcessExecutor');
+        $perforce = $this->getMock(
+            'Composer\Util\Perforce',
+            array('setStream', 'queryP4User', 'writeP4ClientSpec', 'connectClient', 'syncCodeBase'),
+            array($repoConfig, $port, $path, $process, true, 'TEST')
+        );
+        $ref = 'SOURCE_REF';
+        $label = 'LABEL';
+        $perforce->expects($this->at(0))
+        ->method('setStream')
+        ->with($this->equalTo($ref));
+        $perforce->expects($this->at(1))
+        ->method('queryP4User')
+        ->with($this->io);
+        $perforce->expects($this->at(2))
+        ->method('writeP4ClientSpec');
+        $perforce->expects($this->at(3))
+        ->method('connectClient');
+        $perforce->expects($this->at(4))
+        ->method('syncCodeBase')
+        ->with($this->equalTo($label));
+        $downloader->injectPerforce($perforce);
+        $package = $this->getMock('Composer\Package\PackageInterface');
+        $package->expects($this->at(0))
+        ->method('getSourceReference')
+        ->will($this->returnValue($ref));
+        $package->expects($this->at(1))
+        ->method('getPrettyVersion')
+        ->will($this->returnValue($label));
+        $path = $this->testPath;
+        $downloader->doDownload($package, $path);
+    }
+}

+ 134 - 0
tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php

@@ -0,0 +1,134 @@
+<?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\Repository\Vcs;
+
+
+use Composer\Repository\Vcs\PerforceDriver;
+use Composer\Util\Filesystem;
+use Composer\Config;
+
+/**
+ * @author Matt Whittom <Matt.Whittom@veteransunited.com>
+ */
+class PerforceDriverTest extends \PHPUnit_Framework_TestCase
+{
+    private $config;
+    private $io;
+    private $process;
+    private $remoteFileSystem;
+    private $testPath;
+
+    public function setUp()
+    {
+        $this->testPath = sys_get_temp_dir() . '/composer-test';
+        $this->config = new Config();
+        $this->config->merge(
+            array(
+                 'config' => array(
+                     'home' => $this->testPath,
+                 ),
+            )
+        );
+
+        $this->io = $this->getMock('Composer\IO\IOInterface');
+        $this->process = $this->getMock('Composer\Util\ProcessExecutor');
+        $this->remoteFileSystem = $this->getMockBuilder('Composer\Util\RemoteFilesystem')->disableOriginalConstructor()
+                                  ->getMock();
+    }
+
+    public function tearDown()
+    {
+        $fs = new Filesystem;
+        $fs->removeDirectory($this->testPath);
+    }
+
+    public function testInitializeCapturesVariablesFromRepoConfig()
+    {
+        $this->setUp();
+        $repoConfig = array(
+            'url'    => 'TEST_PERFORCE_URL',
+            'depot'  => 'TEST_DEPOT_CONFIG',
+            'branch' => 'TEST_BRANCH_CONFIG'
+        );
+        $driver = new PerforceDriver($repoConfig, $this->io, $this->config, $this->process, $this->remoteFileSystem);
+        $process = $this->getMock('Composer\Util\ProcessExecutor');
+        $arguments = array(
+            array('depot' => 'TEST_DEPOT', 'branch' => 'TEST_BRANCH'),
+            'port' => 'TEST_PORT',
+            'path' => $this->testPath,
+            $process,
+            true,
+            'TEST'
+        );
+        $perforce = $this->getMock('Composer\Util\Perforce', null, $arguments);
+        $driver->injectPerforce($perforce);
+        $driver->initialize();
+        $this->assertEquals('TEST_PERFORCE_URL', $driver->getUrl());
+        $this->assertEquals('TEST_DEPOT_CONFIG', $driver->getDepot());
+        $this->assertEquals('TEST_BRANCH_CONFIG', $driver->getBranch());
+    }
+
+    public function testInitializeLogsInAndConnectsClient()
+    {
+        $this->setUp();
+        $repoConfig = array(
+            'url'    => 'TEST_PERFORCE_URL',
+            'depot'  => 'TEST_DEPOT_CONFIG',
+            'branch' => 'TEST_BRANCH_CONFIG'
+        );
+        $driver = new PerforceDriver($repoConfig, $this->io, $this->config, $this->process, $this->remoteFileSystem);
+        $perforce = $this->getMockBuilder('Composer\Util\Perforce')->disableOriginalConstructor()->getMock();
+        $perforce->expects($this->at(0))
+        ->method('p4Login')
+        ->with($this->io);
+        $perforce->expects($this->at(1))
+        ->method('checkStream')
+        ->with($this->equalTo('TEST_DEPOT_CONFIG'));
+        $perforce->expects($this->at(2))
+        ->method('writeP4ClientSpec');
+        $perforce->expects($this->at(3))
+        ->method('connectClient');
+
+        $driver->injectPerforce($perforce);
+        $driver->initialize();
+    }
+
+    public function testHasComposerFile()
+    {
+        $this->setUp();
+        $repoConfig = array(
+            'url'    => 'TEST_PERFORCE_URL',
+            'depot'  => 'TEST_DEPOT_CONFIG',
+            'branch' => 'TEST_BRANCH_CONFIG'
+        );
+        $driver = new PerforceDriver($repoConfig, $this->io, $this->config, $this->process, $this->remoteFileSystem);
+        $process = $this->getMock('Composer\Util\ProcessExecutor');
+        $arguments = array(
+            array('depot' => 'TEST_DEPOT', 'branch' => 'TEST_BRANCH'),
+            'port' => 'TEST_PORT',
+            'path' => $this->testPath,
+            $process,
+            true,
+            'TEST'
+        );
+        $perforce = $this->getMock('Composer\Util\Perforce', array('getComposerInformation'), $arguments);
+        $perforce->expects($this->at(0))
+        ->method('getComposerInformation')
+        ->with($this->equalTo('//TEST_DEPOT_CONFIG/TEST_IDENTIFIER'))
+        ->will($this->returnValue('Some json stuff'));
+        $driver->injectPerforce($perforce);
+        $driver->initialize();
+        $identifier = 'TEST_IDENTIFIER';
+        $result = $driver->hasComposerFile($identifier);
+        $this->assertTrue($result);
+    }
+}

+ 676 - 0
tests/Composer/Test/Util/PerforceTest.php

@@ -0,0 +1,676 @@
+<?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\Util;
+
+use Composer\Util\Perforce;
+use Composer\Util\ProcessExecutor;
+
+/**
+ * @author Matt Whittom <Matt.Whittom@veteransunited.com>
+ */
+class PerforceTest extends \PHPUnit_Framework_TestCase
+{
+
+    protected $perforce;
+    protected $processExecutor;
+
+    public function setUp()
+    {
+        $this->processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
+        $repoConfig = array(
+            'depot'                       => 'depot',
+            'branch'                      => 'branch',
+            'p4user'                      => 'user',
+            'unique_perforce_client_name' => 'TEST'
+        );
+        $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, true);
+    }
+
+    public function testGetClientWithoutStream()
+    {
+        $client = $this->perforce->getClient();
+        $hostname = gethostname();
+        $timestamp = time();
+
+        $expected = 'composer_perforce_TEST_depot';
+        $this->assertEquals($expected, $client);
+    }
+
+    public function testGetClientFromStream()
+    {
+        $this->setPerforceToStream();
+
+        $client = $this->perforce->getClient();
+
+        $expected = 'composer_perforce_TEST_depot_branch';
+        $this->assertEquals($expected, $client);
+    }
+
+    public function testGetStreamWithoutStream()
+    {
+        $stream = $this->perforce->getStream();
+        $this->assertEquals("//depot", $stream);
+    }
+
+    public function testGetStreamWithStream()
+    {
+        $this->setPerforceToStream();
+
+        $stream = $this->perforce->getStream();
+        $this->assertEquals('//depot/branch', $stream);
+    }
+
+
+    public function testGetStreamWithoutLabelWithStreamWithoutLabel()
+    {
+        $stream = $this->perforce->getStreamWithoutLabel('//depot/branch');
+        $this->assertEquals('//depot/branch', $stream);
+    }
+
+    public function testGetStreamWithoutLabelWithStreamWithLabel()
+    {
+        $stream = $this->perforce->getStreamWithoutLabel('//depot/branching@label');
+        $this->assertEquals('//depot/branching', $stream);
+    }
+
+    public function testGetClientSpec()
+    {
+        $clientSpec = $this->perforce->getP4ClientSpec();
+        $expected = 'path/composer_perforce_TEST_depot.p4.spec';
+        $this->assertEquals($expected, $clientSpec);
+    }
+
+    public function testGenerateP4Command()
+    {
+        $command = 'do something';
+        $p4Command = $this->perforce->generateP4Command($command);
+        $expected = 'p4 -u user -c composer_perforce_TEST_depot -p port do something';
+        $this->assertEquals($expected, $p4Command);
+    }
+
+    public function testQueryP4UserWithUserAlreadySet()
+    {
+        $io = $this->getMock('Composer\IO\IOInterface');
+
+        $repoConfig = array('depot' => 'depot', 'branch' => 'branch', 'p4user' => 'TEST_USER');
+        $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, true, 'TEST');
+
+        $this->perforce->queryP4user($io);
+        $this->assertEquals('TEST_USER', $this->perforce->getUser());
+    }
+
+    public function testQueryP4UserWithUserSetInP4VariablesWithWindowsOS()
+    {
+        $repoConfig = array('depot' => 'depot', 'branch' => 'branch');
+        $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, true, 'TEST');
+
+        $io = $this->getMock('Composer\IO\IOInterface');
+        $expectedCommand = 'p4 set';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = 'P4USER=TEST_P4VARIABLE_USER' . PHP_EOL ;
+                    return true;
+                }
+            )
+        );
+
+        $this->perforce->queryP4user($io);
+        $this->assertEquals('TEST_P4VARIABLE_USER', $this->perforce->getUser());
+    }
+
+    public function testQueryP4UserWithUserSetInP4VariablesNotWindowsOS()
+    {
+        $repoConfig = array('depot' => 'depot', 'branch' => 'branch');
+        $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, 'TEST');
+
+        $io = $this->getMock('Composer\IO\IOInterface');
+        $expectedCommand = 'echo $P4USER';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = 'TEST_P4VARIABLE_USER' . PHP_EOL;
+                    return true;
+                }
+            )
+        );
+
+        $this->perforce->queryP4user($io);
+        $this->assertEquals('TEST_P4VARIABLE_USER', $this->perforce->getUser());
+    }
+
+    public function testQueryP4UserQueriesForUser()
+    {
+        $repoConfig = array('depot' => 'depot', 'branch' => 'branch');
+        $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, 'TEST');
+        $io = $this->getMock('Composer\IO\IOInterface');
+        $expectedQuestion = 'Enter P4 User:';
+        $io->expects($this->at(0))
+        ->method('ask')
+        ->with($this->equalTo($expectedQuestion))
+        ->will($this->returnValue('TEST_QUERY_USER'));
+
+        $this->perforce->queryP4user($io);
+        $this->assertEquals('TEST_QUERY_USER', $this->perforce->getUser());
+    }
+
+    public function testQueryP4UserStoresResponseToQueryForUserWithWindows()
+    {
+        $repoConfig = array('depot' => 'depot', 'branch' => 'branch');
+        $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, true, 'TEST');
+
+        $io = $this->getMock('Composer\IO\IOInterface');
+        $expectedQuestion = 'Enter P4 User:';
+        $io->expects($this->at(0))
+        ->method('ask')
+        ->with($this->equalTo($expectedQuestion))
+        ->will($this->returnValue('TEST_QUERY_USER'));
+        $expectedCommand = 'p4 set P4USER=TEST_QUERY_USER';
+        $this->processExecutor->expects($this->at(1))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will($this->returnValue(0));
+
+        $this->perforce->queryP4user($io);
+    }
+
+    public function testQueryP4UserStoresResponseToQueryForUserWithoutWindows()
+    {
+        $repoConfig = array('depot' => 'depot', 'branch' => 'branch');
+        $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, 'TEST');
+
+        $io = $this->getMock('Composer\IO\IOInterface');
+        $expectedQuestion = 'Enter P4 User:';
+        $io->expects($this->at(0))
+        ->method('ask')
+        ->with($this->equalTo($expectedQuestion))
+        ->will($this->returnValue('TEST_QUERY_USER'));
+        $expectedCommand = 'export P4USER=TEST_QUERY_USER';
+        $this->processExecutor->expects($this->at(1))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will($this->returnValue(0));
+
+        $this->perforce->queryP4user($io);
+    }
+
+    public function testQueryP4PasswordWithPasswordAlreadySet()
+    {
+        $repoConfig = array(
+            'depot'      => 'depot',
+            'branch'     => 'branch',
+            'p4user'     => 'user',
+            'p4password' => 'TEST_PASSWORD'
+        );
+        $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, 'TEST');
+        $io = $this->getMock('Composer\IO\IOInterface');
+
+        $password = $this->perforce->queryP4Password($io);
+        $this->assertEquals('TEST_PASSWORD', $password);
+    }
+
+    public function testQueryP4PasswordWithPasswordSetInP4VariablesWithWindowsOS()
+    {
+        $io = $this->getMock('Composer\IO\IOInterface');
+
+        $expectedCommand = 'p4 set';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = 'P4PASSWD=TEST_P4VARIABLE_PASSWORD' . PHP_EOL;
+                    return true;
+                }
+            )
+        );
+
+        $password = $this->perforce->queryP4Password($io);
+        $this->assertEquals('TEST_P4VARIABLE_PASSWORD', $password);
+    }
+
+    public function testQueryP4PasswordWithPasswordSetInP4VariablesNotWindowsOS()
+    {
+        $repoConfig = array('depot' => 'depot', 'branch' => 'branch', 'p4user' => 'user');
+        $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, 'TEST');
+
+        $io = $this->getMock('Composer\IO\IOInterface');
+        $expectedCommand = 'echo $P4PASSWD';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = 'TEST_P4VARIABLE_PASSWORD' . PHP_EOL;
+                    return true;
+                }
+            )
+        );
+
+        $password = $this->perforce->queryP4Password($io);
+        $this->assertEquals('TEST_P4VARIABLE_PASSWORD', $password);
+    }
+
+    public function testQueryP4PasswordQueriesForPassword()
+    {
+        $io = $this->getMock('Composer\IO\IOInterface');
+        $expectedQuestion = 'Enter password for Perforce user user: ';
+        $io->expects($this->at(0))
+        ->method('askAndHideAnswer')
+        ->with($this->equalTo($expectedQuestion))
+        ->will($this->returnValue('TEST_QUERY_PASSWORD'));
+
+        $password = $this->perforce->queryP4Password($io);
+        $this->assertEquals('TEST_QUERY_PASSWORD', $password);
+    }
+
+    public function testWriteP4ClientSpecWithoutStream()
+    {
+        $stream = fopen('php://memory', 'w+');
+        $this->perforce->writeClientSpecToFile($stream);
+
+        rewind($stream);
+
+        $expectedArray = $this->getExpectedClientSpec(false);
+        try {
+            foreach ($expectedArray as $expected) {
+                $this->assertStringStartsWith($expected, fgets($stream));
+            }
+            $this->assertFalse(fgets($stream));
+        } catch (Exception $e) {
+            fclose($stream);
+            throw $e;
+        }
+        fclose($stream);
+    }
+
+    public function testWriteP4ClientSpecWithStream()
+    {
+        $this->setPerforceToStream();
+        $stream = fopen('php://memory', 'w+');
+
+        $this->perforce->writeClientSpecToFile($stream);
+        rewind($stream);
+
+        $expectedArray = $this->getExpectedClientSpec(true);
+        try {
+            foreach ($expectedArray as $expected) {
+                $this->assertStringStartsWith($expected, fgets($stream));
+            }
+            $this->assertFalse(fgets($stream));
+        } catch (Exception $e) {
+            fclose($stream);
+            throw $e;
+        }
+        fclose($stream);
+    }
+
+    public function testIsLoggedIn()
+    {
+        $expectedCommand = 'p4 -u user -p port login -s';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand), $this->equalTo(null))
+        ->will($this->returnValue(0));
+
+        $this->perforce->isLoggedIn();
+    }
+
+    public function testConnectClient()
+    {
+        $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port client -i < path/composer_perforce_TEST_depot.p4.spec';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand), $this->equalTo(null))
+        ->will($this->returnValue(0));
+
+        $this->perforce->connectClient();
+    }
+
+    public function testGetBranchesWithStream()
+    {
+        $this->setPerforceToStream();
+
+        $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port streams //depot/...';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = 'Stream //depot/branch mainline none \'branch\'' . PHP_EOL;
+                    return true;
+                }
+            )
+        );
+
+        $branches = $this->perforce->getBranches();
+        $this->assertEquals('//depot/branch', $branches['master']);
+    }
+
+    public function testGetBranchesWithoutStream()
+    {
+        $branches = $this->perforce->getBranches();
+        $this->assertEquals('//depot', $branches['master']);
+    }
+
+    public function testGetTagsWithoutStream()
+    {
+        $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port labels';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = 'Label 0.0.1 2013/07/31 \'First Label!\'' . PHP_EOL . 'Label 0.0.2 2013/08/01 \'Second Label!\'' . PHP_EOL;
+                    return true;
+                }
+            )
+        );
+
+        $tags = $this->perforce->getTags();
+        $this->assertEquals('//depot@0.0.1', $tags['0.0.1']);
+        $this->assertEquals('//depot@0.0.2', $tags['0.0.2']);
+    }
+
+    public function testGetTagsWithStream()
+    {
+        $this->setPerforceToStream();
+
+        $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port labels';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = 'Label 0.0.1 2013/07/31 \'First Label!\'' . PHP_EOL . 'Label 0.0.2 2013/08/01 \'Second Label!\'' . PHP_EOL;
+                    return true;
+                }
+            )
+        );
+
+        $tags = $this->perforce->getTags();
+        $this->assertEquals('//depot/branch@0.0.1', $tags['0.0.1']);
+        $this->assertEquals('//depot/branch@0.0.2', $tags['0.0.2']);
+    }
+
+    public function testCheckStreamWithoutStream()
+    {
+        $result = $this->perforce->checkStream('depot');
+        $this->assertFalse($result);
+        $this->assertFalse($this->perforce->isStream());
+    }
+
+    public function testCheckStreamWithStream()
+    {
+        $this->processExecutor->expects($this->any())->method('execute')
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = 'Depot depot 2013/06/25 stream /p4/1/depots/depot/... \'Created by Me\'';
+                    return true;
+                }
+            )
+        );
+        $result = $this->perforce->checkStream('depot');
+        $this->assertTrue($result);
+        $this->assertTrue($this->perforce->isStream());
+    }
+
+    public function testGetComposerInformationWithoutLabelWithoutStream()
+    {
+        $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port  print //depot/composer.json';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = PerforceTest::getComposerJson();
+                    return true;
+                }
+            )
+        );
+
+        $result = $this->perforce->getComposerInformation('//depot');
+        $expected = array(
+            'name'              => 'test/perforce',
+            'description'       => 'Basic project for testing',
+            'minimum-stability' => 'dev',
+            'autoload'          => array('psr-0' => array())
+        );
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testGetComposerInformationWithLabelWithoutStream()
+    {
+        $expectedCommand = 'p4 -u user -p port  files //depot/composer.json@0.0.1';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = '//depot/composer.json#1 - branch change 10001 (text)';
+                    return true;
+                }
+            )
+        );
+
+        $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port  print //depot/composer.json@10001';
+        $this->processExecutor->expects($this->at(1))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = PerforceTest::getComposerJson();
+                    return true;
+                }
+            )
+        );
+
+        $result = $this->perforce->getComposerInformation('//depot@0.0.1');
+
+        $expected = array(
+            'name'              => 'test/perforce',
+            'description'       => 'Basic project for testing',
+            'minimum-stability' => 'dev',
+            'autoload'          => array('psr-0' => array())
+        );
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testGetComposerInformationWithoutLabelWithStream()
+    {
+        $this->setPerforceToStream();
+
+        $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port  print //depot/branch/composer.json';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = PerforceTest::getComposerJson();
+                    return true;
+                }
+            )
+        );
+
+        $result = $this->perforce->getComposerInformation('//depot/branch');
+
+        $expected = array(
+            'name'              => 'test/perforce',
+            'description'       => 'Basic project for testing',
+            'minimum-stability' => 'dev',
+            'autoload'          => array('psr-0' => array())
+        );
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testGetComposerInformationWithLabelWithStream()
+    {
+        $this->setPerforceToStream();
+        $expectedCommand = 'p4 -u user -p port  files //depot/branch/composer.json@0.0.1';
+        $this->processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = '//depot/composer.json#1 - branch change 10001 (text)';
+                    return true;
+                }
+            )
+        );
+
+        $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port  print //depot/branch/composer.json@10001';
+        $this->processExecutor->expects($this->at(1))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will(
+            $this->returnCallback(
+                function ($command, &$output) {
+                    $output = PerforceTest::getComposerJson();
+                    return true;
+                }
+            )
+        );
+
+        $result = $this->perforce->getComposerInformation('//depot/branch@0.0.1');
+
+        $expected = array(
+            'name'              => 'test/perforce',
+            'description'       => 'Basic project for testing',
+            'minimum-stability' => 'dev',
+            'autoload'          => array('psr-0' => array())
+        );
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testSyncCodeBaseWithoutStream()
+    {
+        $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port sync -f @label';
+        $this->processExecutor->expects($this->at(1))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand), $this->equalTo(null))
+        ->will($this->returnValue(0));
+
+        $this->perforce->syncCodeBase('label');
+    }
+
+    public function testSyncCodeBaseWithStream()
+    {
+        $this->setPerforceToStream();
+        $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port sync -f @label';
+        $this->processExecutor->expects($this->at(1))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand))
+        ->will($this->returnValue(0));
+
+        $this->perforce->syncCodeBase('label');
+    }
+
+    public function testCheckServerExists()
+    {
+        $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
+
+        $expectedCommand = 'p4 -p perforce.does.exist:port info -s';
+        $processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand), $this->equalTo(null))
+        ->will($this->returnValue(0));
+
+        $result = $this->perforce->checkServerExists('perforce.does.exist:port', $processExecutor);
+        $this->assertTrue($result);
+    }
+
+    public function testCheckServerExistsWithFailure()
+    {
+        $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
+
+        $expectedCommand = 'p4 -p perforce.does.not.exist:port info -s';
+        $processExecutor->expects($this->at(0))
+        ->method('execute')
+        ->with($this->equalTo($expectedCommand), $this->equalTo(null))
+        ->will($this->returnValue('Perforce client error:'));
+
+        $result = $this->perforce->checkServerExists('perforce.does.not.exist:port', $processExecutor);
+        $this->assertTrue($result);
+    }
+
+    public static function getComposerJson()
+    {
+        $composer_json = array(
+            '{',
+            '"name": "test/perforce",',
+            '"description": "Basic project for testing",',
+            '"minimum-stability": "dev",',
+            '"autoload": {',
+            '"psr-0" : {',
+            '}',
+            '}',
+            '}'
+        );
+
+        return implode($composer_json);
+    }
+
+    private function getExpectedClientSpec($withStream)
+    {
+        $expectedArray = array(
+            'Client: composer_perforce_TEST_depot',
+            PHP_EOL,
+            'Update:',
+            PHP_EOL,
+            'Access:',
+            'Owner:  user',
+            PHP_EOL,
+            'Description:',
+            '  Created by user from composer.',
+            PHP_EOL,
+            'Root: path',
+            PHP_EOL,
+            'Options:  noallwrite noclobber nocompress unlocked modtime rmdir',
+            PHP_EOL,
+            'SubmitOptions:  revertunchanged',
+            PHP_EOL,
+            'LineEnd:  local',
+            PHP_EOL
+        );
+        if ($withStream) {
+            $expectedArray[] = 'Stream:';
+            $expectedArray[] = '  //depot/branch';
+        } else {
+            $expectedArray[] = 'View:  //depot/...  //composer_perforce_TEST_depot/...';
+        }
+
+        return $expectedArray;
+    }
+
+    private function setPerforceToStream()
+    {
+        $this->perforce->setStream('//depot/branch');
+    }
+}