- <?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();
- }
- }