PageRenderTime 41ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/toolkit/ezptestrunner.php

https://github.com/zerustech/ezpublish
PHP | 469 lines | 386 code | 18 blank | 65 comment | 10 complexity | 5354c295db9517ac7e9b58232d22d8f9 MD5 | raw file
  1. <?php
  2. /**
  3. * File containing the ezpTestRunner class
  4. *
  5. * @copyright Copyright (C) eZ Systems AS. All rights reserved.
  6. * @license For full copyright and license information view LICENSE file distributed with this source code.
  7. * @version //autogentag//
  8. * @package tests
  9. */
  10. class ezpTestRunner extends PHPUnit_TextUI_Command
  11. {
  12. /**
  13. * @var ezpTestRunner
  14. */
  15. static private $instance = null;
  16. /**
  17. * Enables additional parameters for the test runner
  18. */
  19. public function __construct()
  20. {
  21. $this->longOptions['list-suites'] = 'handleListSuites';
  22. $this->longOptions['list-tests'] = 'handleListTests';
  23. $this->longOptions['dsn='] = 'handleDsn';
  24. $this->longOptions['db-per-test'] = 'handleDbPerTest';
  25. // Default values
  26. $this->arguments['list-suites'] = false;
  27. $this->arguments['list-tests'] = false;
  28. $this->arguments['dsn'] = '';
  29. $this->arguments['db-per-test'] = false;
  30. }
  31. /**
  32. * Returns the ezpTestRunner instance
  33. *
  34. * @return ezpTestRunner
  35. */
  36. static public function instance()
  37. {
  38. if( self::$instance === null )
  39. {
  40. self::$instance = new ezpTestRunner;
  41. }
  42. return self::$instance;
  43. }
  44. /**
  45. * Return the argument $argumentName given on the command line
  46. * $argumentName must be the long option name ( 'configuration' ), not the short option name ( 'c' )
  47. *
  48. * @param $argumentName
  49. *
  50. * @return mixed|null
  51. */
  52. public function getLongOption( $argumentName )
  53. {
  54. if ( array_key_exists( $argumentName, $this->longOptions ) || array_key_exists( "$argumentName=", $this->longOptions ) )
  55. {
  56. if ( array_key_exists( $argumentName, $this->arguments ) )
  57. {
  58. return $this->arguments[$argumentName];
  59. }
  60. else
  61. {
  62. return null;
  63. }
  64. }
  65. else
  66. {
  67. var_dump( "Error: Invalid argument name '{$argumentName}'" );
  68. var_dump( $this->longOptions );
  69. die;
  70. }
  71. }
  72. /**
  73. * If called, will output a list of all available tests
  74. *
  75. * @see handleCustomTestSuite()
  76. */
  77. public function handleListTests()
  78. {
  79. $this->arguments['list-tests'] = true;
  80. }
  81. /**
  82. * Stores the given DSN string into the runners arguments array
  83. *
  84. * @see dsn()
  85. * @param string $value
  86. */
  87. public function handleDsn( $value )
  88. {
  89. $this->arguments['dsn'] = $value;
  90. }
  91. /**
  92. * Sets the argument 'db-per-test' to true, which means that every test will be
  93. * performed on a clean database
  94. */
  95. public function handleDbPerTest()
  96. {
  97. $this->arguments['db-per-test'] = true;
  98. }
  99. /**
  100. * Extends PHPUnit's default help text with the additional options coming with
  101. * this runner
  102. */
  103. protected function showHelp()
  104. {
  105. parent::showHelp();
  106. print <<<EOT
  107. --db-per-test Use a clean database per test
  108. --dsn <resource> Use the database specified with a DSN: type://user:password@host/database.
  109. An example to connect with the local MySQL database is:
  110. mysqli://root:mypass@localhost/unittests
  111. --list-suites Lists all suites
  112. --list-tests Lists all tests
  113. EOT;
  114. }
  115. /**
  116. * Returns the eZ Publish test suite
  117. *
  118. * If $directories are given, only the tests in these directories will be executed.
  119. * If omitted, the default eZ Publish test suite and all extension test suites will be executed.
  120. *
  121. * @param array $directories
  122. * @return ezpTestSuite $suite
  123. */
  124. protected function prepareTests( array $directories = array() )
  125. {
  126. // If no $directories are given, we return the standard eZ Publish test suite
  127. if ( count( $directories ) === 0 )
  128. {
  129. // The default eZ Publish test suite with all core tests
  130. $suite = new eZTestSuite();
  131. // Add the extension directories to search for test suites
  132. $directories = eZDir::findSubitems( eZExtension::baseDirectory(), 'dl', true );
  133. }
  134. else
  135. {
  136. $suite = new ezpTestSuite();
  137. $suite->setName( "eZ Publish Test Suite" );
  138. }
  139. foreach ( $directories as $dir )
  140. {
  141. $file = $dir;
  142. if ( strpos( $file, 'suite.php' ) === false )
  143. {
  144. $file = eZDir::path( array( $file, "suite.php" ) );
  145. }
  146. if ( !file_exists( $file ) )
  147. {
  148. $normalizedExtDir = $this->normalizeExtensionPath( $dir );
  149. $file = eZDir::path( array( $normalizedExtDir, "tests/suite.php") );
  150. }
  151. if ( file_exists( $file ) )
  152. {
  153. require_once( $file );
  154. $class = self::getClassName( $file );
  155. $suite->addTest( new $class );
  156. }
  157. else
  158. {
  159. // No suite.php found anywhere in given (extension) directory.
  160. print( "No tests found for $dir\n" );
  161. }
  162. }
  163. return $suite;
  164. }
  165. /**
  166. * Returns the default test suite including all tests in all extensions.
  167. *
  168. * @return ezpTestSuite $suite
  169. */
  170. static public function suite()
  171. {
  172. if ( !class_exists( 'eZTestSuite', true ) )
  173. {
  174. echo "\nThe eZTestSuite class isn't defined. Are the tests autoloads generated ?\n";
  175. echo "You can generate them using php bin/php/ezpgenerateautoloads.php -s\n\n";
  176. exit( PHPUnit_TextUI_TestRunner::FAILURE_EXIT );
  177. }
  178. $suite = new eZTestSuite;
  179. // Add suites from extensions.
  180. $extensions = eZDir::findSubitems( eZExtension::baseDirectory(), 'dl', true );
  181. foreach( $extensions as $extension )
  182. {
  183. $suiteFile = eZDir::path( array( $extension, "tests", "suite.php" ) );
  184. if ( file_exists( $suiteFile ) )
  185. {
  186. $suite->addTestFile( $suiteFile );
  187. }
  188. }
  189. return $suite;
  190. }
  191. /**
  192. * Checks if an argument has been given to the testrunner and uses it to prepare the tests
  193. * If set, the test runner will ONLY execute the given tests and omit the kernel
  194. * @see prepareTests()
  195. */
  196. protected function handleCustomTestSuite()
  197. {
  198. $this->arguments['test'] = $this->prepareTests( $this->options[1] );
  199. if( $this->arguments['list-suites'] )
  200. {
  201. $this->listSuites( $this->arguments['test'] );
  202. }
  203. if( $this->arguments['list-tests'] )
  204. {
  205. $this->listTests( $this->arguments['test'] );
  206. }
  207. }
  208. /**
  209. * Displays a list of available tests
  210. *
  211. * @param ezpTestSuite $suite
  212. */
  213. protected function listTests( ezpTestSuite $suite )
  214. {
  215. $tests = $this->getTests( $suite );
  216. print "Available test(s):\n";
  217. foreach( $tests as $test )
  218. {
  219. print "- {$test}\n";
  220. }
  221. exit( PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
  222. }
  223. /**
  224. * Prepends the extension path to $path if not already in $path
  225. *
  226. * @param string $path
  227. * @return string
  228. */
  229. protected function normalizeExtensionPath( $path )
  230. {
  231. if ( strpos( $path, eZExtension::baseDirectory() ) === false )
  232. $path = eZDir::path( array( eZExtension::baseDirectory(), $path ) );
  233. return $path;
  234. }
  235. /**
  236. * Returns the first class name found inside of $file or false if no class is in the file
  237. *
  238. * @param string $file
  239. * @return string|bool
  240. */
  241. static public function getClassName( $file )
  242. {
  243. // $file argument has / directory separator, but the getFileName() method of ReflectionClass
  244. // returns a path with platform specific style of directory separator
  245. if ( DIRECTORY_SEPARATOR != '/' )
  246. {
  247. $file = strtr( $file, '/', DIRECTORY_SEPARATOR );
  248. }
  249. // Resolve symlinks and expand path to file
  250. $file = realpath( $file );
  251. $classes = get_declared_classes();
  252. $size = count( $classes );
  253. $total = $size > 30 ? 30 : $size;
  254. // check only the last 30 classes.
  255. for ( $i = $size - 1; $i > $size - $total - 1; $i-- )
  256. {
  257. $rf = new ReflectionClass( $classes[$i] );
  258. $len = strlen( $file );
  259. if ( strcmp( $file, substr( $rf->getFileName(), -$len ) ) == 0 )
  260. {
  261. return $classes[$i];
  262. }
  263. }
  264. return false;
  265. }
  266. /**
  267. * Returns an ezpDsn object created from the dsn console input
  268. *
  269. * @return ezpDsn dsn
  270. */
  271. static public function dsn()
  272. {
  273. $testRunner = ezpTestRunner::instance();
  274. return new ezpDsn( $testRunner->arguments['dsn'] );
  275. }
  276. /**
  277. * Returns true/false if the database should be created per test
  278. *
  279. * @return bool
  280. */
  281. static public function dbPerTest()
  282. {
  283. $testRunner = ezpTestRunner::instance();
  284. return $testRunner->arguments['db-per-test'];
  285. }
  286. /**
  287. * Additionally ensures that the required argument "--dsn" is set
  288. *
  289. * @inheritdoc
  290. * @param array $argv
  291. */
  292. protected function handleArguments(array $argv)
  293. {
  294. parent::handleArguments($argv);
  295. if ( empty( $this->arguments['dsn'] ) )
  296. {
  297. PHPUnit_TextUI_TestRunner::showError( 'The parameter --dsn is required' );
  298. }
  299. }
  300. /**
  301. * If called, will output a list of all available suites
  302. *
  303. * @see handleCustomTestSuite()
  304. */
  305. public function handleListSuites()
  306. {
  307. $this->arguments['list-suites'] = true;
  308. }
  309. /**
  310. * Displays a list of available test suites
  311. *
  312. * @param ezpTestSuite $suite
  313. */
  314. public function listSuites( ezpTestSuite $suite )
  315. {
  316. $suites = $this->getSuites( $suite );
  317. print "Available suite(s):\n";
  318. foreach( $suites as $s )
  319. {
  320. print "- {$s}\n";
  321. }
  322. exit( PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
  323. }
  324. /**
  325. * Returns an array with the names of all available test suites which are children of $suite
  326. *
  327. * @param ezpTestSuite $suite
  328. * @return array
  329. */
  330. protected function getSuites( ezpTestSuite $suite )
  331. {
  332. $suites = array();
  333. /** @var PHPUnit_Framework_Test[]|ezpTestSuite[] $tests */
  334. $tests = $suite->tests();
  335. foreach( $tests as $test )
  336. {
  337. $reflectionClass = new ReflectionClass( $test );
  338. if ( $reflectionClass->isSubclassOf( 'PHPUnit_Framework_TestSuite' ) )
  339. {
  340. $suites[] = $reflectionClass->getName();
  341. }
  342. if ( $reflectionClass->isSubclassOf( 'ezpTestSuite' ) )
  343. {
  344. $suites = array_merge( $suites, $this->getSuites( $test ) );
  345. }
  346. }
  347. sort( $suites );
  348. return $suites;
  349. }
  350. /**
  351. * Returns an array with the names of all available tests which are children of $suite
  352. *
  353. * @param ezpTestSuite $suite
  354. * @return string[]
  355. */
  356. protected function getTests( ezpTestSuite $suite )
  357. {
  358. $tests = array();
  359. $iterator = $suite->getIterator();
  360. /** @var PHPUnit_Framework_TestCase $test */
  361. foreach ( $iterator as $test )
  362. {
  363. $reflectionClass = new ReflectionClass( $test );
  364. $tests[] = $reflectionClass->getName() . "::" . $test->getName();
  365. }
  366. sort( $tests );
  367. return $tests;
  368. }
  369. /**
  370. * Creates a test runner with eZ Publish specific code coverage configuration
  371. *
  372. * @inheritdoc
  373. * @return PHPUnit_TextUI_TestRunner
  374. */
  375. protected function createRunner()
  376. {
  377. $filter = $this->createCodeCoverageFilter();
  378. return new PHPUnit_TextUI_TestRunner($this->arguments['loader'], $filter );
  379. }
  380. /**
  381. * Whitelists all kernel, lib and extension classes for code coverage
  382. *
  383. * @return PHP_CodeCoverage_Filter
  384. */
  385. protected function createCodeCoverageFilter()
  386. {
  387. $filter = new PHP_CodeCoverage_Filter();
  388. // Add kernel classes to whitelist
  389. $kernelClassFiles = require 'autoload/ezp_kernel.php';
  390. $filter->addFilesToWhitelist( $kernelClassFiles );
  391. // Add extension classes to whitelist
  392. if ( !is_file( 'var/autoload/ezp_extension.php' ) )
  393. {
  394. PHPUnit_TextUI_TestRunner::showError(
  395. "Please generate the extension autoloads first.\n"
  396. ."You can generate them using php bin/php/ezpgenerateautoloads.php -e"
  397. );
  398. }
  399. $extensionClassFiles = require 'var/autoload/ezp_extension.php';
  400. $filter->addFilesToWhitelist( $extensionClassFiles );
  401. return $filter;
  402. }
  403. }
  404. ?>