TestUtility.php #1

  • //
  • guest/
  • perforce_software/
  • chronicle/
  • main/
  • tests/
  • phpunit/
  • TestUtility.php
  • View
  • Commits
  • Open Download .zip Download (24 KB)
<?php
/**
 * Utility methods for running test suites.
 *
 * @copyright   2011 Perforce Software. All rights reserved.
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
 * @version     <release>/<patch>
 */
class TestUtility
{
    private $_testClass;
    private $_testMethod;
    private $_logFile;
    private $_p4Params  = array();
    private $_testSites = array();

    const TEST_MAX_TRY_COUNT = 1000;

    /**
     * Constructor for this class
     *
     * @param  string   $testClass   The name of class of the test
     * @param  string   $testMethod  The name of method of the test
     */
    public function __construct($testClass, $testMethod)
    {
        $this->_testClass  = $testClass;
        $this->_testMethod = $testMethod;

        $this->_logFile = TEST_LOG_PATH .'/'. $testClass .'-'. $testMethod .'.log';

        // configure p4 connection params.
        $serverRoot      = TEST_DATA_PATH .'/server-'. $testClass .'-'. $testMethod;
        $clientRoot      = TEST_DATA_PATH .'/clients-'. $testClass .'-'. $testMethod;
        $this->_p4Params = array(
            'serverRoot' => $serverRoot,
            'clientRoot' => $clientRoot,
            'port'       => 'rsh:' . P4D_PATH . ' -iqr ' . $serverRoot . ' -J off '
                            . '-vtrack=0 -vserver.locks.dir=disabled',
            'user'       => 'tester',
            'client'     => 'test-client',
            'group'      => 'test-group',
            'password'   => 'testing123'
        );

        // define at least one site configuration for testing purposes.
        $prefix = '//' . P4Cms_Site::SITE_PREFIX;
        $suffix = '/'  . P4Cms_Site::DEFAULT_BRANCH;
        $this->_testSites = array(
            $prefix . 'test' . $suffix => array(
                'urls' => array(
                    defined('HTTP_HOST') ? preg_replace('#^http://#', '', HTTP_HOST) : 'test-host.com'
                )
            )
        );
    }

    /**
     * Get Perforce config parameters
     *
     * @param  string   $param   Optional - specific Perforce parameter to get
     *
     * @return mixed    A specific Perforce parameter, or all parameters
     */
    public function getP4Params($param = null)
    {
        $params = $this->_p4Params;
        if ($param) {
            return isset($params[$param]) ? $params[$param] : null;
        }
        return $params;
    }

    /**
     * Get test sites configuration
     *
     * @return array    A site configuration array
     */
    public function getTestSites()
    {
        return $this->_testSites;
    }

    /**
     * Create a Perforce connection for testing. The perforce connection will
     * connect using a p4d started with the -i (run for inetd) flag.
     *
     * @param  string   $type   Allow caller to force the API implementation.
     *
     * @return P4_Connection_Interface  A Perforce API implementation.
     */
    public function createP4Connection($type = null)
    {
        // make sure we're using the bundled p4/p4d.
        require_once(APPLICATION_PATH . '/Bootstrap.php');
        Bootstrap::initPath();

        extract($this->_p4Params);

        if (!is_dir($serverRoot)) {
            throw new P4_Exception('Unable to create new server.');
        }

        // create connection.
        $p4 = P4_Connection::factory(
            $port, $user, $client, $password, null, $type
        );

        // create user.
        $userForm = array(
            'User'     => $user,
            'Email'    => $user . '@testhost',
            'FullName' => 'Test User',
            'Password' => $password
        );
        $p4->run('user', '-i', $userForm);
        $p4->run('login', array(), $password);

        // establish protections.
        // This looks like a no-op, but remember that fresh P4 servers consider
        // every user to be a superuser. These operations make only the configured
        // user a superuser, and subsequent users will be 'normal' users.
        $result = $p4->run('protect', '-o');
        $protect = $result->getData(0);
        $p4->run('protect', '-i', $protect);

        // create client
        $clientForm = array(
            'Client'    => $client,
            'Owner'     => $user,
            'Root'      => $clientRoot .'/superuser',
            'View'      => array('//depot/... //'. $client .'/...')
        );
        $p4->run('client', '-i', $clientForm);

        $this->openPermissions($serverRoot, true);

        return $p4;
    }

    /**
     * Recursively remove a directory and all of it's file contents.
     *
     * @param  string   $directory   The directory to remove.
     * @param  boolean  $recursive   when true, recursively delete directories.
     * @param  boolean  $removeRoot  when true, remove the root (passed) directory too
     */
    public function removeDirectory($directory, $recursive = true, $removeRoot = true)
    {
        if (is_dir($directory)) {
            $files = new RecursiveDirectoryIterator($directory);
            foreach ($files as $file) {
                if ($files->isDot()) {
                    continue;
                }
                if ($file->isFile()) {
                    // on Windows, it may take some time for open file handles to
                    // be closed.  We try to unlink a file for TEST_MAX_TRY_COUNT
                    // times and then bail out.
                    $count = 0;
                    chmod($file->getPathname(), 0777);
                    while ($count <= self::TEST_MAX_TRY_COUNT) {
                        try {
                            unlink($file->getPathname());
                            break;
                        } catch (Exception $e) {
                            $count++;
                            if ($count == self::TEST_MAX_TRY_COUNT) {
                                throw new Exception(
                                    "Can't delete '" . $file->getPathname() . "' with message ".$e->getMessage()
                                );
                            }
                        }
                    }
                } elseif ($file->isDir() && $recursive) {
                    $this->removeDirectory($file->getPathname(), true, true);
                }
            }

            if ($removeRoot) {
                chmod($directory, 0777);
                $count = 0;
                while ($count <= self::TEST_MAX_TRY_COUNT) {
                    try {
                        rmdir($directory);
                        break;
                    } catch (Exception $e) {
                        $count++;
                        if ($count == self::TEST_MAX_TRY_COUNT) {
                            throw new Exception(
                                "Can't delete '" . $directory->getPathname() . "' with message ".$e->getMessage()
                            );
                        }
                    }
                }
            }
        }
    }

    /**
     * Removes any existing sites (streams) and creates new depots
     * and 'live' streams based on the passed sites array.
     *
     * @param   array   $sites  sites to create.
     */
    public function saveSites($sites)
    {
        $p4     = $this->p4;
        $result = $p4->run('streams');
        foreach ($result->getData() as $stream) {
            // remove any clients of this stream so we can delete it
            $clients = $p4->run('clients', array('-S', $stream['Stream']));
            foreach ($clients->getData() as $client) {
                $p4->run('client', array('-d', '-f', $client['client']));
            }

            $p4->run('stream', array('-d', '-f', $stream['Stream']));
        }

        // we're done if no sites were passed; return early
        if (empty($sites)) {
            return;
        }

        // remember our original test client
        // (site->setConnection will change it below)
        $client = $p4->getClient();

        foreach ($sites as $id => $config) {
            preg_match('#^//([^/]+)/(.+)#', $id, $matches);
            $depot  = $matches[1];
            $stream = $matches[2];

            $input = array(
                'Depot'     => $depot,
                'Type'      => 'stream',
                'Map'       => $depot . '/...'
            );
            $result = $p4->run('depot', '-i', $input);

            $input = array(
                'Stream'    => $id,
                'Name'      => $stream,
                'Parent'    => 'none',
                'Type'      => 'mainline',
                'Owner'     => $p4->getUser(),
                'Paths'     => array('share ...')
            );
            $result = $p4->run('stream', '-i', $input);

            // write site branch config.
            $site = new P4Cms_Site;
            $site->setId($id)
                 ->setConnection($p4)
                 ->getConfig()
                 ->setValues($config)
                 ->save();
        }

        // force the disconnect callback(s) the site objects
        // added to run prior to setting back our client
        $p4->disconnect()->connect();

        // restore original client.
        $p4->setClient($client);
    }

    /**
     * Generate the test sites file.
     *
     * @todo    consider using setup controller's createSite() method
     *          to more faithfully mimic properly configured sites.
     */
    public function createTestSites()
    {
        P4Cms_Site::setSitesPackagesPath(TEST_SITES_PATH);
        P4Cms_Site::setSitesDataPath(TEST_DATA_PATH . '/sites');

        // save test sites.
        $this->saveSites($this->_testSites);

        // configure environment for first site.
        $firstSite              = reset($this->_testSites);
        $_SERVER['HTTP_HOST']   = $firstSite['urls'][0];
        $_SERVER['REQUEST_URI'] = '/';

        // create built-in system roles for first site.
        $site = P4Cms_Site::fetch(key($this->_testSites));
        $site->getConfig()
             ->setTitle('testsite')
             ->setDescription('description of the test site')
             ->save();
        $this->createSiteRoles($site);
    }

    /**
     * Create the built-in system roles (member and admin)
     *
     * @param P4Cms_Site    $site   site object
     */
    public function createSiteRoles($site)
    {
        $adapter = $site->getStorageAdapter();
        $user    = $this->getP4Params('user');

        // create the base site group and add system user to it.
        $siteGroup = new P4_Group($adapter->getConnection());
        $siteGroup->setId($adapter->getProperty(P4Cms_Acl_Role::PARENT_GROUP))
                  ->setUsers(array($user))
                  ->save();

        // create an administrator role
        $role = new P4Cms_Acl_Role;
        $role->setAdapter($adapter)
             ->setId(P4Cms_Acl_Role::ROLE_ADMINISTRATOR)
             ->setUsers(array($user))
             ->save();

        // create a member role
        $role = new P4Cms_Acl_Role;
        $role->setAdapter($adapter)
             ->setId(P4Cms_Acl_Role::ROLE_MEMBER)
             ->addOwner($user)
             ->setUsers(array($user))
             ->save();
    }

    /**
     * Remove all sites.
     */
    public function removeSites()
    {
        return $this->saveSites(array());
    }

    /**
     * Ensure that directories needed for testing exist.
     */
    public function createTestDirectories()
    {
        // remove existing directories to start fresh w. each test.
        $this->removeTestDirectories();

        extract($this->_p4Params);

        $directories = array(
            TEST_DATA_PATH,
            $serverRoot,
            $clientRoot,
            $clientRoot . '/superuser',
            $clientRoot . '/testuser',
            TEST_SESSION_SAVE_PATH,
            TEST_DATA_PATH . '/sites'
        );

        foreach ($directories as $directory) {
            if (!is_dir($directory)) {
                mkdir($directory, 0777, true);
            }
        }
    }

    /**
     * Remove test directories (clean-up).
     */
    public function removeTestDirectories()
    {
        // remove the entire data directory since it is test-specific
        $this->removeDirectory(TEST_DATA_PATH);
    }

    /**
     * Open up permissions (possibly recursively) on a directory. All files
     * in the directory (including the directory itself) will be given a
     * permission mask of 0777. This method checks that the owner of the
     * running PHP process owns each file before it attempts to change
     * permissions on it.
     *
     * @param  string  $directory  the directory to change permissions on.
     * @param  bool    $recursive  optional - whether to do so recursively.
     */
    private function openPermissions($directory, $recursive = false)
    {
        $uid   = getmyuid();
        $files = new RecursiveDirectoryIterator($directory);

        foreach ($files as $file) {
            $stat = stat($file->getPathname());
            if ($stat['uid'] != $uid) continue; // skip files we don't own
            if (!chmod($file->getPathname(), 0777)) {
                throw new Exception(
                    "Can't set permissions on '" . $file->getPathname() . "'"
                );
            }
            if ($file->isDir() && $recursive) {
                if ($files->isDot()) {
                    continue;
                }
                $this->openPermissions($file->getPathname(), $recursive);
            }
        }

        chmod($directory, 0777);
    }

    /**
     * Setup the logger to direct to a file based on the class/method
     */
    public function setUpLogger()
    {
        if (!is_dir(dirname($this->_logFile))) {
            mkdir(dirname($this->_logFile), 0777, true);
        }
        if (is_file($this->_logFile)) {
            chmod($this->_logFile, 0777);
            unlink($this->_logFile);
        }

        $writer   = new Zend_Log_Writer_Stream($this->_logFile);
        $logger   = new Zend_Log($writer);
        P4_Log::setLogger($logger);
    }

    /**
     * If the test passed, remove the log
     *
     * @param  boolean  $failed  Indicates whether the recently completed test has failed.
     */
    public function tearDownLogger($failed)
    {
        P4_Log::getLogger()->__destruct();
        P4Cms_Log::getLogger()->__destruct();

        // Disable teardown for testing
        return;

        // if we didn't fail, remove the log file
        if (!$failed && is_file($this->_logFile)) {
            chmod($this->_logFile, 0777);
            unlink($this->_logFile);
        }
    }

    /**
     * Add core modules to the include path.
     * Some tests depend on availability of core module facilities.
     */
    public function initCoreModules()
    {
        P4Cms_Module::reset();

        // tell p4cms module where to find core modules.
        P4Cms_Module::setCoreModulesPath(APPLICATION_PATH);

        // init.
        $modules = P4Cms_Module::fetchAllCore();
        foreach ($modules as $module) {
            $module->init();
        }
    }

    /**
     * Dump variable content into a string within test context.
     *
     * @param  mixed  $var  variable to dump
     */
    public function dumper($var)
    {
        ob_start();
        var_dump($var);
        return ob_get_clean();
    }

    /**
     * Perform application bootstrap.
     *
     * @param   string|null              $environment   optional - The application environment to use
     * @param   string|array|Zend_Config $options       String path to bootstrap configuration file,
     *                                                  or array/Zend_Config of configuration options
     * @return  Zend_Application         the zend application instance we ran boostrap on
     */
    public function doBootstrap($environment = null, $options = null)
    {
        $application = new P4Cms_Application(
            $environment ?: APPLICATION_ENV,
            $options ?: array('resources' => array('perforce' => $this->_p4Params))
        );
        $application->bootstrap();

        // explicitly enable the stream wrapper so that when the tests
        // are run in a PHP with short tags disabled, the tests can
        // complete successfully.
        $view = Zend_Layout::getMvcInstance()->getView();
        $view->setUseStreamWrapper(true);

        // re-introduce sites/all/modules because $site->load()
        // of the test site will remove it in favor of mock modules
        P4Cms_Module::addPackagesPath(MODULE_PATH);

        return $application;
    }

    /**
     * Simulate post of a file input field where no file has been selected.
     *
     * @param  string  $field  The name for the file input form field.
     */
    public function simulateEmptyFileInput($field)
    {
        if (!is_array($_FILES)) {
            $_FILES = array();
        }

        $_FILES[$field] = array(
            'name'      => "",
            'type'      => null,
            'tmp_name'  => null,
            'error'     => UPLOAD_ERR_NO_FILE,
            'size'      => 0
        );
    }

    /**
     * Reset zend library components.
     */
    public function resetZend()
    {
        Zend_Registry::_unsetInstance();
        $front = Zend_Controller_Front::getInstance();
        $front->resetInstance();
        Zend_Layout::resetMvcInstance();
    }

    /**
     * Reset p4 library components.
     */
    public function resetP4()
    {
        // clear default connection
        if (class_exists('P4_Connection', false)) {
            P4_Connection::clearDefaultConnection();
        }

        // clear out shutdown callbacks
        if (class_exists('P4_Environment', false)) {
            P4_Environment::setShutdownCallbacks(null);
        }

        // clear logger.
        if (class_exists('P4_Log', false)) {
            P4_Log::setLogger(null);
        }
    }

    /**
     * Reset p4cms library components.
     */
    public function resetP4Cms()
    {
        // clear active user.
        if (class_exists('P4Cms_User', false)) {
            P4Cms_User::clearActive();
        }

        // reset theme.
        if (class_exists('P4Cms_Theme', false)) {
            P4Cms_Theme::reset();
        }

        // reset site component.
        if (class_exists('P4Cms_Site', false)) {
            P4Cms_Site::clearActive();
            P4Cms_Site::setSitesPackagesPath(null);
            P4Cms_Site::setSitesDataPath(null);
        }

        // clear pub/sub provider.
        if (class_exists('P4Cms_PubSub', false)) {
            P4Cms_PubSub::setInstance(new P4Cms_PubSub_Provider);
        }

        // reset modules.
        if (class_exists('P4Cms_Module', false)) {
            P4Cms_Module::reset();
        }

        // clear default adapter.
        if (class_exists('P4Cms_Record', false)) {
            P4Cms_Record::clearDefaultAdapter();
        }

        // clear logger.
        if (class_exists('P4Cms_Log', false)) {
            if (P4Cms_Log::hasLogger()) {
                $logger = P4Cms_Log::getLogger();
                $logger->__destruct();
            }

            P4Cms_Log::setLogger(null);
        }

        // clear any registered form plugin paths.
        if (class_exists('P4Cms_Form', false)) {
            P4Cms_Form::clearPrefixPathRegistry();
        }

        // clear cache of widget types
        if (class_exists('P4Cms_Widget_Type', false)) {
            P4Cms_Widget_Type::clearCache();
        }

        // clear active ACL.
        if (class_exists('P4Cms_Acl', false)) {
            P4Cms_Acl::setActive(null);
        }

        // clear loader packages.
        if (class_exists('P4Cms_Loader', false)) {
            P4Cms_Loader::setPackagePaths(array());
        }

        // clear static cache manager.
        if (class_exists('P4Cms_Loader', false)) {
            P4Cms_Cache::setManager(null);
        }
    }

    /**
     * Reset the application
     */
    public function resetApplication()
    {
        // purge the search instance from search module; if present
        if (class_exists('Search_Module', false)) {
            if (Search_Module::hasSearchInstance()) {
                $proxy           = Search_Module::factory();
                $proxyReflection = new ReflectionObject($proxy);
                $proxyIndex      = $proxyReflection->getProperty('_index');
                $proxyIndex->setAccessible(true);
                $index           = $proxyIndex->getValue($proxy);
                $index->__destruct();

                Search_Module::clearSearchInstances();
            }
        }

        // clear out auto-loader
        spl_autoload_unregister(array('P4Cms_Loader', 'autoload'));
    }

    /**
     * Perform setup of a library test.
     *
     * @param  PHPUnit_Test  $test  An instance of a not-yet-run test.
     */
    public function setUp($test)
    {
        $this->createTestDirectories();
        $this->setUpLogger();

        $test->p4 = $this->createP4Connection();
        $this->p4 = $test->p4;

        Zend_Session::$_unitTestEnabled = true;
    }

    /**
     * Perform setup of a module test
     *
     * @param   PHPUnit_Test             $test          An instance of a not-yet-run test.
     * @param   string|null              $environment   optional - The application environment to use
     * @param   string|array|Zend_Config $options       String path to bootstrap configuration file,
     *                                                  or array/Zend_Config of configuration options
     */
    public function setUpModuleTest($test, $environment = null, $options = null)
    {
        // add sites/all/modules to packages.
        P4Cms_Module::addPackagesPath(
            dirname(APPLICATION_PATH) . '/sites/all/modules'
        );

        $this->createTestSites();

        if ($test instanceof Zend_Test_PHPUnit_ControllerTestCase) {
            $utility = $this;
            $test->bootstrap = function() use ($utility, $environment, $options, $test)
            {
                $test->bootstrap = $utility->doBootstrap($environment, $options);
            };
        } else {
            $this->doBootstrap($environment, $options);
        }
    }

    /**
     * Perform tear down of a library test
     *
     * @param  PHPUnit_Test  $test  An instance of a just-completed test.
     */
    public function tearDown($test)
    {
        // call p4 library shutdown functions
        // (closes all p4 connections, cleans up temp specs)
        if (class_exists('P4_Environment', false)) {
            P4_Environment::runShutdownCallbacks();
        }

        // disconnect the P4 connection, if exists
        if (isset($test->p4)) $test->p4->disconnect();

        $this->tearDownLogger($test->hasFailed());
        $this->resetP4Cms();
        $this->resetP4();
        $this->resetZend();

        // forces collection of any existing garbage cycles
        // so no open file handles prevent files/directories
        // from being removed.
        gc_collect_cycles();

        $this->removeTestDirectories();
    }

    /**
     * Perform tear down of a module test
     *
     * @param  PHPUnit_Test  $test  An instance of a just-completed test.
     */
    public function tearDownModuleTest($test)
    {
        $this->resetApplication();
    }

    /**
     * Impersonate a logged-in user with the given role (e.g. member,
     * administrator, ...). Installs the default ACL and the named role.
     *
     * @param   string      $roleId     the id of the role to act as.
     * @param   P4Cms_Site  $site       the site to impersonate the role on
     *
     */
    public function impersonate($roleId, P4Cms_Site $site = null)
    {
        $site = $site ?: P4Cms_Site::fetchActive();
        $acl  = $site->getAcl();

        // if role is not anonymous (virtual) we need
        // to create it and authenticate a mock user
        if ($roleId != P4Cms_Acl_Role::ROLE_ANONYMOUS) {

            // create pretend user.
            $user = new P4Cms_User;
            $user->setId('mweiss')
                 ->setFullName('Michael T. Weiss')
                 ->setEmail('mweiss@thepretender.tv')
                 ->setPersonalAdapter(P4Cms_Record::getDefaultAdapter())
                 ->save();
            P4Cms_User::setActive($user);

            // assign user to named role.
            $role = new P4Cms_Acl_Role();
            $role->setId($roleId)
                 ->setUsers(array($user->getId()))
                 ->save();

            $auth = Zend_Auth::getInstance();
            $auth->authenticate($user);

            // update acl roles.
            $acl->setRoles(P4Cms_Acl_Role::fetchAll());

        }

        // update acl defaults now that named role exists.
        $acl->installDefaults();
    }
}
# Change User Description Committed
#1 16170 perforce_software Move Chronicle files to follow new path scheme for branching.
//guest/perforce_software/chronicle/tests/phpunit/TestUtility.php
#1 8972 Matt Attaway Initial add of the Chronicle source code