1f8369d7dSTobias Sarnowski<?php 2e1d9dcc8SAndreas Gohr 33a7140a1SAndreas Gohruse dokuwiki\Extension\PluginController; 4cbb44eabSAndreas Gohruse dokuwiki\Extension\Event; 5e1d9dcc8SAndreas Gohruse dokuwiki\Extension\EventHandler; 6f8369d7dSTobias Sarnowski/** 7f8369d7dSTobias Sarnowski * Helper class to provide basic functionality for tests 83c1490b3SPhy * 93c1490b3SPhy * @uses PHPUnit_Framework_TestCase and thus PHPUnit 5.7+ is required 10f8369d7dSTobias Sarnowski */ 11bd9dab32SAndreas Gohrabstract class DokuWikiTest extends PHPUnit\Framework\TestCase { 12f8369d7dSTobias Sarnowski 13f8369d7dSTobias Sarnowski /** 14f8369d7dSTobias Sarnowski * tests can override this 15f8369d7dSTobias Sarnowski * 16f8369d7dSTobias Sarnowski * @var array plugins to enable for test class 17f8369d7dSTobias Sarnowski */ 18f8369d7dSTobias Sarnowski protected $pluginsEnabled = array(); 19f8369d7dSTobias Sarnowski 20f8369d7dSTobias Sarnowski /** 21f8369d7dSTobias Sarnowski * tests can override this 22f8369d7dSTobias Sarnowski * 23f8369d7dSTobias Sarnowski * @var array plugins to disable for test class 24f8369d7dSTobias Sarnowski */ 25f8369d7dSTobias Sarnowski protected $pluginsDisabled = array(); 26f8369d7dSTobias Sarnowski 27f8369d7dSTobias Sarnowski /** 28bd9dab32SAndreas Gohr * setExpectedException was deprecated in PHPUnit 6 29bd9dab32SAndreas Gohr * 30bd9dab32SAndreas Gohr * @param string $class 31bd9dab32SAndreas Gohr * @param null|string $message 32bd9dab32SAndreas Gohr */ 33bd9dab32SAndreas Gohr public function setExpectedException($class, $message=null) { 34bd9dab32SAndreas Gohr $this->expectException($class); 35bd9dab32SAndreas Gohr if(!is_null($message)) { 36bd9dab32SAndreas Gohr $this->expectExceptionMessage($message); 37bd9dab32SAndreas Gohr } 38bd9dab32SAndreas Gohr } 39bd9dab32SAndreas Gohr 40bd9dab32SAndreas Gohr /** 410644090aSAndreas Gohr * Setup the data directory 420644090aSAndreas Gohr * 430644090aSAndreas Gohr * This is ran before each test class 440644090aSAndreas Gohr */ 45*1c33cec3SAndreas Gohr public static function setUpBeforeClass() : void { 460644090aSAndreas Gohr // just to be safe not to delete something undefined later 470644090aSAndreas Gohr if(!defined('TMP_DIR')) die('no temporary directory'); 480644090aSAndreas Gohr if(!defined('DOKU_TMP_DATA')) die('no temporary data directory'); 490644090aSAndreas Gohr 501c0be3ebSAndreas Gohr self::setupDataDir(); 511c0be3ebSAndreas Gohr self::setupConfDir(); 520644090aSAndreas Gohr } 530644090aSAndreas Gohr 540644090aSAndreas Gohr /** 55f8369d7dSTobias Sarnowski * Reset the DokuWiki environment before each test run. Makes sure loaded config, 56f8369d7dSTobias Sarnowski * language and plugins are correct. 57f8369d7dSTobias Sarnowski * 58f8369d7dSTobias Sarnowski * @throws Exception if plugin actions fail 59f8369d7dSTobias Sarnowski * @return void 60f8369d7dSTobias Sarnowski */ 61*1c33cec3SAndreas Gohr public function setUp() : void { 620644090aSAndreas Gohr 63f8369d7dSTobias Sarnowski // reload config 64f8369d7dSTobias Sarnowski global $conf, $config_cascade; 65f8369d7dSTobias Sarnowski $conf = array(); 66f8369d7dSTobias Sarnowski foreach (array('default','local','protected') as $config_group) { 67f8369d7dSTobias Sarnowski if (empty($config_cascade['main'][$config_group])) continue; 68f8369d7dSTobias Sarnowski foreach ($config_cascade['main'][$config_group] as $config_file) { 6979e79377SAndreas Gohr if (file_exists($config_file)) { 70f8369d7dSTobias Sarnowski include($config_file); 71f8369d7dSTobias Sarnowski } 72f8369d7dSTobias Sarnowski } 73f8369d7dSTobias Sarnowski } 74f8369d7dSTobias Sarnowski 75f8369d7dSTobias Sarnowski // reload license config 76f8369d7dSTobias Sarnowski global $license; 77f8369d7dSTobias Sarnowski $license = array(); 78f8369d7dSTobias Sarnowski 79f8369d7dSTobias Sarnowski // load the license file(s) 80f8369d7dSTobias Sarnowski foreach (array('default','local') as $config_group) { 81f8369d7dSTobias Sarnowski if (empty($config_cascade['license'][$config_group])) continue; 82f8369d7dSTobias Sarnowski foreach ($config_cascade['license'][$config_group] as $config_file) { 8379e79377SAndreas Gohr if(file_exists($config_file)){ 84f8369d7dSTobias Sarnowski include($config_file); 85f8369d7dSTobias Sarnowski } 86f8369d7dSTobias Sarnowski } 87f8369d7dSTobias Sarnowski } 88f48e16abSGerrit Uitslag // reload some settings 89f48e16abSGerrit Uitslag $conf['gzip_output'] &= (strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip') !== false); 90f8369d7dSTobias Sarnowski 91f48e16abSGerrit Uitslag if($conf['compression'] == 'bz2' && !DOKU_HAS_BZIP) { 92f48e16abSGerrit Uitslag $conf['compression'] = 'gz'; 93f48e16abSGerrit Uitslag } 94f48e16abSGerrit Uitslag if($conf['compression'] == 'gz' && !DOKU_HAS_GZIP) { 95f48e16abSGerrit Uitslag $conf['compression'] = 0; 96f48e16abSGerrit Uitslag } 97f8369d7dSTobias Sarnowski // make real paths and check them 98f8369d7dSTobias Sarnowski init_paths(); 99f8369d7dSTobias Sarnowski init_files(); 100f8369d7dSTobias Sarnowski 101f8369d7dSTobias Sarnowski // reset loaded plugins 102f8369d7dSTobias Sarnowski global $plugin_controller_class, $plugin_controller; 1033a7140a1SAndreas Gohr /** @var PluginController $plugin_controller */ 104f8369d7dSTobias Sarnowski $plugin_controller = new $plugin_controller_class(); 105f8369d7dSTobias Sarnowski 106f8369d7dSTobias Sarnowski // disable all non-default plugins 107f8369d7dSTobias Sarnowski global $default_plugins; 108f8369d7dSTobias Sarnowski foreach ($plugin_controller->getList() as $plugin) { 109f8369d7dSTobias Sarnowski if (!in_array($plugin, $default_plugins)) { 110f8369d7dSTobias Sarnowski if (!$plugin_controller->disable($plugin)) { 111f8369d7dSTobias Sarnowski throw new Exception('Could not disable plugin "'.$plugin.'"!'); 112f8369d7dSTobias Sarnowski } 113f8369d7dSTobias Sarnowski } 114f8369d7dSTobias Sarnowski } 115f8369d7dSTobias Sarnowski 116f8369d7dSTobias Sarnowski // disable and enable configured plugins 117f8369d7dSTobias Sarnowski foreach ($this->pluginsDisabled as $plugin) { 118f8369d7dSTobias Sarnowski if (!$plugin_controller->disable($plugin)) { 119f8369d7dSTobias Sarnowski throw new Exception('Could not disable plugin "'.$plugin.'"!'); 120f8369d7dSTobias Sarnowski } 121f8369d7dSTobias Sarnowski } 122f8369d7dSTobias Sarnowski foreach ($this->pluginsEnabled as $plugin) { 123f8369d7dSTobias Sarnowski /* enable() returns false but works... 124f8369d7dSTobias Sarnowski if (!$plugin_controller->enable($plugin)) { 125f8369d7dSTobias Sarnowski throw new Exception('Could not enable plugin "'.$plugin.'"!'); 126f8369d7dSTobias Sarnowski } 127f8369d7dSTobias Sarnowski */ 128f8369d7dSTobias Sarnowski $plugin_controller->enable($plugin); 129f8369d7dSTobias Sarnowski } 130f8369d7dSTobias Sarnowski 131f8369d7dSTobias Sarnowski // reset event handler 132f8369d7dSTobias Sarnowski global $EVENT_HANDLER; 133e1d9dcc8SAndreas Gohr $EVENT_HANDLER = new EventHandler(); 134f8369d7dSTobias Sarnowski 135f8369d7dSTobias Sarnowski // reload language 136f8369d7dSTobias Sarnowski $local = $conf['lang']; 137cbb44eabSAndreas Gohr Event::createAndTrigger('INIT_LANG_LOAD', $local, 'init_lang', true); 138c01cdb70SChristopher Smith 139c01cdb70SChristopher Smith global $INPUT; 140ccc4c71cSAndreas Gohr $INPUT = new \dokuwiki\Input\Input(); 141f8369d7dSTobias Sarnowski } 142db5867f1SAndreas Gohr 143db5867f1SAndreas Gohr /** 1441c0be3ebSAndreas Gohr * Reinitialize the data directory for this class run 1451c0be3ebSAndreas Gohr */ 1461c0be3ebSAndreas Gohr public static function setupDataDir() { 1471c0be3ebSAndreas Gohr // remove any leftovers from the last run 1481c0be3ebSAndreas Gohr if(is_dir(DOKU_TMP_DATA)) { 1491c0be3ebSAndreas Gohr // clear indexer data and cache 1501c0be3ebSAndreas Gohr idx_get_indexer()->clear(); 1511c0be3ebSAndreas Gohr TestUtils::rdelete(DOKU_TMP_DATA); 1521c0be3ebSAndreas Gohr } 1531c0be3ebSAndreas Gohr 1541c0be3ebSAndreas Gohr // populate default dirs 1551c0be3ebSAndreas Gohr TestUtils::rcopy(TMP_DIR, __DIR__ . '/../data/'); 1561c0be3ebSAndreas Gohr } 1571c0be3ebSAndreas Gohr 1581c0be3ebSAndreas Gohr /** 1591c0be3ebSAndreas Gohr * Reinitialize the conf directory for this class run 1601c0be3ebSAndreas Gohr */ 1611c0be3ebSAndreas Gohr public static function setupConfDir() { 1621c0be3ebSAndreas Gohr $defaults = [ 1631c0be3ebSAndreas Gohr 'acronyms.conf', 1641c0be3ebSAndreas Gohr 'dokuwiki.php', 1651c0be3ebSAndreas Gohr 'entities.conf', 1661c0be3ebSAndreas Gohr 'interwiki.conf', 1671c0be3ebSAndreas Gohr 'license.php', 1681c0be3ebSAndreas Gohr 'manifest.json', 1691c0be3ebSAndreas Gohr 'mediameta.php', 1701c0be3ebSAndreas Gohr 'mime.conf', 1711c0be3ebSAndreas Gohr 'plugins.php', 1721c0be3ebSAndreas Gohr 'plugins.required.php', 1731c0be3ebSAndreas Gohr 'scheme.conf', 1741c0be3ebSAndreas Gohr 'smileys.conf', 1751c0be3ebSAndreas Gohr 'wordblock.conf' 1761c0be3ebSAndreas Gohr ]; 1771c0be3ebSAndreas Gohr 1781c0be3ebSAndreas Gohr // clear any leftovers 1791c0be3ebSAndreas Gohr if(is_dir(DOKU_CONF)) { 1801c0be3ebSAndreas Gohr TestUtils::rdelete(DOKU_CONF); 1811c0be3ebSAndreas Gohr } 1821c0be3ebSAndreas Gohr mkdir(DOKU_CONF); 1831c0be3ebSAndreas Gohr 1841c0be3ebSAndreas Gohr // copy defaults 1851c0be3ebSAndreas Gohr foreach($defaults as $file) { 1861c0be3ebSAndreas Gohr copy(DOKU_INC . '/conf/' . $file, DOKU_CONF . $file); 1871c0be3ebSAndreas Gohr } 1881c0be3ebSAndreas Gohr 1891c0be3ebSAndreas Gohr // copy test files 1901c0be3ebSAndreas Gohr TestUtils::rcopy(TMP_DIR, __DIR__ . '/../conf'); 1911c0be3ebSAndreas Gohr } 1921c0be3ebSAndreas Gohr 1931c0be3ebSAndreas Gohr /** 194d732617bSAndreas Gohr * Waits until a new second has passed 195d732617bSAndreas Gohr * 1964af692c2SAndreas Gohr * This tried to be clever about the passing of time and return early if possible. Unfortunately 1974af692c2SAndreas Gohr * this never worked reliably fo unknown reasons. To avoid flaky tests, this now always simply 1984af692c2SAndreas Gohr * sleeps for a full second on every call. 199d732617bSAndreas Gohr * 2004af692c2SAndreas Gohr * @param bool $init no longer used 201d732617bSAndreas Gohr * @return int new timestamp 202d732617bSAndreas Gohr */ 203d732617bSAndreas Gohr protected function waitForTick($init = false) { 2044af692c2SAndreas Gohr sleep(1); 2054af692c2SAndreas Gohr return time(); 206d732617bSAndreas Gohr } 207210ff133SAndreas Gohr 208210ff133SAndreas Gohr /** 209210ff133SAndreas Gohr * Allow for testing inaccessible methods (private or protected) 210210ff133SAndreas Gohr * 211210ff133SAndreas Gohr * This makes it easier to test protected methods without needing to create intermediate 212210ff133SAndreas Gohr * classes inheriting and changing the access. 213210ff133SAndreas Gohr * 214210ff133SAndreas Gohr * @link https://stackoverflow.com/a/8702347/172068 215210ff133SAndreas Gohr * @param object $obj Object in which to call the method 216210ff133SAndreas Gohr * @param string $func The method to call 217210ff133SAndreas Gohr * @param array $args The arguments to call the method with 218210ff133SAndreas Gohr * @return mixed 219210ff133SAndreas Gohr * @throws ReflectionException when the given obj/func does not exist 220210ff133SAndreas Gohr */ 221210ff133SAndreas Gohr protected static function callInaccessibleMethod($obj, $func, array $args) { 222210ff133SAndreas Gohr $class = new \ReflectionClass($obj); 223210ff133SAndreas Gohr $method = $class->getMethod($func); 224210ff133SAndreas Gohr $method->setAccessible(true); 225210ff133SAndreas Gohr return $method->invokeArgs($obj, $args); 226210ff133SAndreas Gohr } 227836f6efbSAndreas Gohr 228836f6efbSAndreas Gohr /** 229836f6efbSAndreas Gohr * Allow for reading inaccessible properties (private or protected) 230836f6efbSAndreas Gohr * 231836f6efbSAndreas Gohr * This makes it easier to check internals of tested objects. This should generally 232836f6efbSAndreas Gohr * be avoided. 233836f6efbSAndreas Gohr * 234836f6efbSAndreas Gohr * @param object $obj Object on which to access the property 235836f6efbSAndreas Gohr * @param string $prop name of the property to access 236836f6efbSAndreas Gohr * @return mixed 237836f6efbSAndreas Gohr * @throws ReflectionException when the given obj/prop does not exist 238836f6efbSAndreas Gohr */ 239836f6efbSAndreas Gohr protected static function getInaccessibleProperty($obj, $prop) { 240836f6efbSAndreas Gohr $class = new \ReflectionClass($obj); 241836f6efbSAndreas Gohr $property = $class->getProperty($prop); 242836f6efbSAndreas Gohr $property->setAccessible(true); 243836f6efbSAndreas Gohr return $property->getValue($obj); 244836f6efbSAndreas Gohr } 245836f6efbSAndreas Gohr 246836f6efbSAndreas Gohr /** 247836f6efbSAndreas Gohr * Allow for reading inaccessible properties (private or protected) 248836f6efbSAndreas Gohr * 249836f6efbSAndreas Gohr * This makes it easier to set internals of tested objects. This should generally 250836f6efbSAndreas Gohr * be avoided. 251836f6efbSAndreas Gohr * 252836f6efbSAndreas Gohr * @param object $obj Object on which to access the property 253836f6efbSAndreas Gohr * @param string $prop name of the property to access 254836f6efbSAndreas Gohr * @param mixed $value new value to set the property to 255836f6efbSAndreas Gohr * @return void 256836f6efbSAndreas Gohr * @throws ReflectionException when the given obj/prop does not exist 257836f6efbSAndreas Gohr */ 258836f6efbSAndreas Gohr protected static function setInaccessibleProperty($obj, $prop, $value) { 259836f6efbSAndreas Gohr $class = new \ReflectionClass($obj); 260836f6efbSAndreas Gohr $property = $class->getProperty($prop); 261836f6efbSAndreas Gohr $property->setAccessible(true); 262836f6efbSAndreas Gohr $property->setValue($obj, $value); 263836f6efbSAndreas Gohr } 264f8369d7dSTobias Sarnowski} 265