1f8369d7dSTobias Sarnowski<?php 2e1d9dcc8SAndreas Gohr 33a7140a1SAndreas Gohruse dokuwiki\Extension\PluginController; 4cbb44eabSAndreas Gohruse dokuwiki\Extension\Event; 5e1d9dcc8SAndreas Gohruse dokuwiki\Extension\EventHandler; 6*f577a2efSAndreas Gohruse dokuwiki\Logger; 7*f577a2efSAndreas Gohr 8f8369d7dSTobias Sarnowski/** 9f8369d7dSTobias Sarnowski * Helper class to provide basic functionality for tests 103c1490b3SPhy * 113c1490b3SPhy * @uses PHPUnit_Framework_TestCase and thus PHPUnit 5.7+ is required 12f8369d7dSTobias Sarnowski */ 13bd9dab32SAndreas Gohrabstract class DokuWikiTest extends PHPUnit\Framework\TestCase { 14f8369d7dSTobias Sarnowski 15f8369d7dSTobias Sarnowski /** 16f8369d7dSTobias Sarnowski * tests can override this 17f8369d7dSTobias Sarnowski * 18f8369d7dSTobias Sarnowski * @var array plugins to enable for test class 19f8369d7dSTobias Sarnowski */ 20f8369d7dSTobias Sarnowski protected $pluginsEnabled = array(); 21f8369d7dSTobias Sarnowski 22f8369d7dSTobias Sarnowski /** 23f8369d7dSTobias Sarnowski * tests can override this 24f8369d7dSTobias Sarnowski * 25f8369d7dSTobias Sarnowski * @var array plugins to disable for test class 26f8369d7dSTobias Sarnowski */ 27f8369d7dSTobias Sarnowski protected $pluginsDisabled = array(); 28f8369d7dSTobias Sarnowski 29f8369d7dSTobias Sarnowski /** 30bd9dab32SAndreas Gohr * setExpectedException was deprecated in PHPUnit 6 31bd9dab32SAndreas Gohr * 32bd9dab32SAndreas Gohr * @param string $class 33bd9dab32SAndreas Gohr * @param null|string $message 34bd9dab32SAndreas Gohr */ 35bd9dab32SAndreas Gohr public function setExpectedException($class, $message=null) { 36bd9dab32SAndreas Gohr $this->expectException($class); 37bd9dab32SAndreas Gohr if(!is_null($message)) { 38bd9dab32SAndreas Gohr $this->expectExceptionMessage($message); 39bd9dab32SAndreas Gohr } 40bd9dab32SAndreas Gohr } 41bd9dab32SAndreas Gohr 42bd9dab32SAndreas Gohr /** 430644090aSAndreas Gohr * Setup the data directory 440644090aSAndreas Gohr * 450644090aSAndreas Gohr * This is ran before each test class 460644090aSAndreas Gohr */ 471c33cec3SAndreas Gohr public static function setUpBeforeClass() : void { 480644090aSAndreas Gohr // just to be safe not to delete something undefined later 490644090aSAndreas Gohr if(!defined('TMP_DIR')) die('no temporary directory'); 500644090aSAndreas Gohr if(!defined('DOKU_TMP_DATA')) die('no temporary data directory'); 510644090aSAndreas Gohr 521c0be3ebSAndreas Gohr self::setupDataDir(); 531c0be3ebSAndreas Gohr self::setupConfDir(); 540644090aSAndreas Gohr } 550644090aSAndreas Gohr 560644090aSAndreas Gohr /** 57f8369d7dSTobias Sarnowski * Reset the DokuWiki environment before each test run. Makes sure loaded config, 58f8369d7dSTobias Sarnowski * language and plugins are correct. 59f8369d7dSTobias Sarnowski * 60f8369d7dSTobias Sarnowski * @throws Exception if plugin actions fail 61f8369d7dSTobias Sarnowski * @return void 62f8369d7dSTobias Sarnowski */ 631c33cec3SAndreas Gohr public function setUp() : void { 64dfa189a8SAndreas Gohr // reset execution time if it's enabled 65dfa189a8SAndreas Gohr if(ini_get('max_execution_time') > 0) { 66dfa189a8SAndreas Gohr set_time_limit(90); 67dfa189a8SAndreas Gohr } 680644090aSAndreas Gohr 69f8369d7dSTobias Sarnowski // reload config 70f8369d7dSTobias Sarnowski global $conf, $config_cascade; 71f8369d7dSTobias Sarnowski $conf = array(); 72f8369d7dSTobias Sarnowski foreach (array('default','local','protected') as $config_group) { 73f8369d7dSTobias Sarnowski if (empty($config_cascade['main'][$config_group])) continue; 74f8369d7dSTobias Sarnowski foreach ($config_cascade['main'][$config_group] as $config_file) { 7579e79377SAndreas Gohr if (file_exists($config_file)) { 76f8369d7dSTobias Sarnowski include($config_file); 77f8369d7dSTobias Sarnowski } 78f8369d7dSTobias Sarnowski } 79f8369d7dSTobias Sarnowski } 80f8369d7dSTobias Sarnowski 81f8369d7dSTobias Sarnowski // reload license config 82f8369d7dSTobias Sarnowski global $license; 83f8369d7dSTobias Sarnowski $license = array(); 84f8369d7dSTobias Sarnowski 85f8369d7dSTobias Sarnowski // load the license file(s) 86f8369d7dSTobias Sarnowski foreach (array('default','local') as $config_group) { 87f8369d7dSTobias Sarnowski if (empty($config_cascade['license'][$config_group])) continue; 88f8369d7dSTobias Sarnowski foreach ($config_cascade['license'][$config_group] as $config_file) { 8979e79377SAndreas Gohr if(file_exists($config_file)){ 90f8369d7dSTobias Sarnowski include($config_file); 91f8369d7dSTobias Sarnowski } 92f8369d7dSTobias Sarnowski } 93f8369d7dSTobias Sarnowski } 94f48e16abSGerrit Uitslag // reload some settings 95f48e16abSGerrit Uitslag $conf['gzip_output'] &= (strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip') !== false); 96f8369d7dSTobias Sarnowski 97f48e16abSGerrit Uitslag if($conf['compression'] == 'bz2' && !DOKU_HAS_BZIP) { 98f48e16abSGerrit Uitslag $conf['compression'] = 'gz'; 99f48e16abSGerrit Uitslag } 100f48e16abSGerrit Uitslag if($conf['compression'] == 'gz' && !DOKU_HAS_GZIP) { 101f48e16abSGerrit Uitslag $conf['compression'] = 0; 102f48e16abSGerrit Uitslag } 103f8369d7dSTobias Sarnowski // make real paths and check them 1043366d071SAndreas Gohr init_creationmodes(); 105f8369d7dSTobias Sarnowski init_paths(); 106f8369d7dSTobias Sarnowski init_files(); 107f8369d7dSTobias Sarnowski 108f8369d7dSTobias Sarnowski // reset loaded plugins 109f8369d7dSTobias Sarnowski global $plugin_controller_class, $plugin_controller; 1103a7140a1SAndreas Gohr /** @var PluginController $plugin_controller */ 111f8369d7dSTobias Sarnowski $plugin_controller = new $plugin_controller_class(); 112f8369d7dSTobias Sarnowski 113f8369d7dSTobias Sarnowski // disable all non-default plugins 114f8369d7dSTobias Sarnowski global $default_plugins; 115f8369d7dSTobias Sarnowski foreach ($plugin_controller->getList() as $plugin) { 116f8369d7dSTobias Sarnowski if (!in_array($plugin, $default_plugins)) { 117f8369d7dSTobias Sarnowski if (!$plugin_controller->disable($plugin)) { 118f8369d7dSTobias Sarnowski throw new Exception('Could not disable plugin "'.$plugin.'"!'); 119f8369d7dSTobias Sarnowski } 120f8369d7dSTobias Sarnowski } 121f8369d7dSTobias Sarnowski } 122f8369d7dSTobias Sarnowski 123f8369d7dSTobias Sarnowski // disable and enable configured plugins 124f8369d7dSTobias Sarnowski foreach ($this->pluginsDisabled as $plugin) { 125f8369d7dSTobias Sarnowski if (!$plugin_controller->disable($plugin)) { 126f8369d7dSTobias Sarnowski throw new Exception('Could not disable plugin "'.$plugin.'"!'); 127f8369d7dSTobias Sarnowski } 128f8369d7dSTobias Sarnowski } 129f8369d7dSTobias Sarnowski foreach ($this->pluginsEnabled as $plugin) { 130f8369d7dSTobias Sarnowski /* enable() returns false but works... 131f8369d7dSTobias Sarnowski if (!$plugin_controller->enable($plugin)) { 132f8369d7dSTobias Sarnowski throw new Exception('Could not enable plugin "'.$plugin.'"!'); 133f8369d7dSTobias Sarnowski } 134f8369d7dSTobias Sarnowski */ 135f8369d7dSTobias Sarnowski $plugin_controller->enable($plugin); 136f8369d7dSTobias Sarnowski } 137f8369d7dSTobias Sarnowski 138f8369d7dSTobias Sarnowski // reset event handler 139f8369d7dSTobias Sarnowski global $EVENT_HANDLER; 140e1d9dcc8SAndreas Gohr $EVENT_HANDLER = new EventHandler(); 141f8369d7dSTobias Sarnowski 142f8369d7dSTobias Sarnowski // reload language 143f8369d7dSTobias Sarnowski $local = $conf['lang']; 144cbb44eabSAndreas Gohr Event::createAndTrigger('INIT_LANG_LOAD', $local, 'init_lang', true); 145c01cdb70SChristopher Smith 146c01cdb70SChristopher Smith global $INPUT; 147ccc4c71cSAndreas Gohr $INPUT = new \dokuwiki\Input\Input(); 148f8369d7dSTobias Sarnowski } 149db5867f1SAndreas Gohr 150db5867f1SAndreas Gohr /** 1511c0be3ebSAndreas Gohr * Reinitialize the data directory for this class run 1521c0be3ebSAndreas Gohr */ 1531c0be3ebSAndreas Gohr public static function setupDataDir() { 1541c0be3ebSAndreas Gohr // remove any leftovers from the last run 1551c0be3ebSAndreas Gohr if(is_dir(DOKU_TMP_DATA)) { 1561c0be3ebSAndreas Gohr // clear indexer data and cache 1571c0be3ebSAndreas Gohr idx_get_indexer()->clear(); 1581c0be3ebSAndreas Gohr TestUtils::rdelete(DOKU_TMP_DATA); 1591c0be3ebSAndreas Gohr } 1601c0be3ebSAndreas Gohr 1611c0be3ebSAndreas Gohr // populate default dirs 1621c0be3ebSAndreas Gohr TestUtils::rcopy(TMP_DIR, __DIR__ . '/../data/'); 1631c0be3ebSAndreas Gohr } 1641c0be3ebSAndreas Gohr 1651c0be3ebSAndreas Gohr /** 1661c0be3ebSAndreas Gohr * Reinitialize the conf directory for this class run 1671c0be3ebSAndreas Gohr */ 1681c0be3ebSAndreas Gohr public static function setupConfDir() { 1691c0be3ebSAndreas Gohr $defaults = [ 1701c0be3ebSAndreas Gohr 'acronyms.conf', 1711c0be3ebSAndreas Gohr 'dokuwiki.php', 1721c0be3ebSAndreas Gohr 'entities.conf', 1731c0be3ebSAndreas Gohr 'interwiki.conf', 1741c0be3ebSAndreas Gohr 'license.php', 1751c0be3ebSAndreas Gohr 'manifest.json', 1761c0be3ebSAndreas Gohr 'mediameta.php', 1771c0be3ebSAndreas Gohr 'mime.conf', 1781c0be3ebSAndreas Gohr 'plugins.php', 1791c0be3ebSAndreas Gohr 'plugins.required.php', 1801c0be3ebSAndreas Gohr 'scheme.conf', 1811c0be3ebSAndreas Gohr 'smileys.conf', 1821c0be3ebSAndreas Gohr 'wordblock.conf' 1831c0be3ebSAndreas Gohr ]; 1841c0be3ebSAndreas Gohr 1851c0be3ebSAndreas Gohr // clear any leftovers 1861c0be3ebSAndreas Gohr if(is_dir(DOKU_CONF)) { 1871c0be3ebSAndreas Gohr TestUtils::rdelete(DOKU_CONF); 1881c0be3ebSAndreas Gohr } 1891c0be3ebSAndreas Gohr mkdir(DOKU_CONF); 1901c0be3ebSAndreas Gohr 1911c0be3ebSAndreas Gohr // copy defaults 1921c0be3ebSAndreas Gohr foreach($defaults as $file) { 1931c0be3ebSAndreas Gohr copy(DOKU_INC . '/conf/' . $file, DOKU_CONF . $file); 1941c0be3ebSAndreas Gohr } 1951c0be3ebSAndreas Gohr 1961c0be3ebSAndreas Gohr // copy test files 1971c0be3ebSAndreas Gohr TestUtils::rcopy(TMP_DIR, __DIR__ . '/../conf'); 1981c0be3ebSAndreas Gohr } 1991c0be3ebSAndreas Gohr 2001c0be3ebSAndreas Gohr /** 201d732617bSAndreas Gohr * Waits until a new second has passed 202d732617bSAndreas Gohr * 2034af692c2SAndreas Gohr * This tried to be clever about the passing of time and return early if possible. Unfortunately 204e937d004SSatoshi Sahara * this never worked reliably for unknown reasons. To avoid flaky tests, this now always simply 2054af692c2SAndreas Gohr * sleeps for a full second on every call. 206d732617bSAndreas Gohr * 2074af692c2SAndreas Gohr * @param bool $init no longer used 208d732617bSAndreas Gohr * @return int new timestamp 209d732617bSAndreas Gohr */ 210d732617bSAndreas Gohr protected function waitForTick($init = false) { 2114af692c2SAndreas Gohr sleep(1); 2124af692c2SAndreas Gohr return time(); 213d732617bSAndreas Gohr } 214210ff133SAndreas Gohr 215210ff133SAndreas Gohr /** 216210ff133SAndreas Gohr * Allow for testing inaccessible methods (private or protected) 217210ff133SAndreas Gohr * 218210ff133SAndreas Gohr * This makes it easier to test protected methods without needing to create intermediate 219210ff133SAndreas Gohr * classes inheriting and changing the access. 220210ff133SAndreas Gohr * 221210ff133SAndreas Gohr * @link https://stackoverflow.com/a/8702347/172068 222210ff133SAndreas Gohr * @param object $obj Object in which to call the method 223210ff133SAndreas Gohr * @param string $func The method to call 224210ff133SAndreas Gohr * @param array $args The arguments to call the method with 225210ff133SAndreas Gohr * @return mixed 226210ff133SAndreas Gohr * @throws ReflectionException when the given obj/func does not exist 227210ff133SAndreas Gohr */ 228210ff133SAndreas Gohr protected static function callInaccessibleMethod($obj, $func, array $args) { 229210ff133SAndreas Gohr $class = new \ReflectionClass($obj); 230210ff133SAndreas Gohr $method = $class->getMethod($func); 231210ff133SAndreas Gohr $method->setAccessible(true); 232210ff133SAndreas Gohr return $method->invokeArgs($obj, $args); 233210ff133SAndreas Gohr } 234836f6efbSAndreas Gohr 235836f6efbSAndreas Gohr /** 236836f6efbSAndreas Gohr * Allow for reading inaccessible properties (private or protected) 237836f6efbSAndreas Gohr * 238836f6efbSAndreas Gohr * This makes it easier to check internals of tested objects. This should generally 239836f6efbSAndreas Gohr * be avoided. 240836f6efbSAndreas Gohr * 241836f6efbSAndreas Gohr * @param object $obj Object on which to access the property 242836f6efbSAndreas Gohr * @param string $prop name of the property to access 243836f6efbSAndreas Gohr * @return mixed 244836f6efbSAndreas Gohr * @throws ReflectionException when the given obj/prop does not exist 245836f6efbSAndreas Gohr */ 246836f6efbSAndreas Gohr protected static function getInaccessibleProperty($obj, $prop) { 247836f6efbSAndreas Gohr $class = new \ReflectionClass($obj); 248836f6efbSAndreas Gohr $property = $class->getProperty($prop); 249836f6efbSAndreas Gohr $property->setAccessible(true); 250836f6efbSAndreas Gohr return $property->getValue($obj); 251836f6efbSAndreas Gohr } 252836f6efbSAndreas Gohr 253836f6efbSAndreas Gohr /** 254836f6efbSAndreas Gohr * Allow for reading inaccessible properties (private or protected) 255836f6efbSAndreas Gohr * 256836f6efbSAndreas Gohr * This makes it easier to set internals of tested objects. This should generally 257836f6efbSAndreas Gohr * be avoided. 258836f6efbSAndreas Gohr * 259836f6efbSAndreas Gohr * @param object $obj Object on which to access the property 260836f6efbSAndreas Gohr * @param string $prop name of the property to access 261836f6efbSAndreas Gohr * @param mixed $value new value to set the property to 262836f6efbSAndreas Gohr * @return void 263836f6efbSAndreas Gohr * @throws ReflectionException when the given obj/prop does not exist 264836f6efbSAndreas Gohr */ 265836f6efbSAndreas Gohr protected static function setInaccessibleProperty($obj, $prop, $value) { 266836f6efbSAndreas Gohr $class = new \ReflectionClass($obj); 267836f6efbSAndreas Gohr $property = $class->getProperty($prop); 268836f6efbSAndreas Gohr $property->setAccessible(true); 269836f6efbSAndreas Gohr $property->setValue($obj, $value); 270836f6efbSAndreas Gohr } 271*f577a2efSAndreas Gohr 272*f577a2efSAndreas Gohr /** 273*f577a2efSAndreas Gohr * Expect the next log message to contain $message 274*f577a2efSAndreas Gohr * 275*f577a2efSAndreas Gohr * @param string $facility 276*f577a2efSAndreas Gohr * @param string $message 277*f577a2efSAndreas Gohr * @return void 278*f577a2efSAndreas Gohr */ 279*f577a2efSAndreas Gohr protected function expectLogMessage(string $message, string $facility = Logger::LOG_ERROR): void 280*f577a2efSAndreas Gohr { 281*f577a2efSAndreas Gohr $logger = Logger::getInstance($facility); 282*f577a2efSAndreas Gohr $logger->expect($message); 283*f577a2efSAndreas Gohr } 284f8369d7dSTobias Sarnowski} 285