1<?php 2 3use dokuwiki\Extension\EventHandler; 4 5if(!class_exists('PHPUnit_Framework_TestCase')) { 6 /** 7 * phpunit 5/6 compatibility 8 */ 9 class PHPUnit_Framework_TestCase extends PHPUnit\Framework\TestCase { 10 /** 11 * setExpectedException is deprecated in PHPUnit 6 12 * 13 * @param string $class 14 * @param null|string $message 15 */ 16 public function setExpectedException($class, $message=null) { 17 $this->expectException($class); 18 if(!is_null($message)) { 19 $this->expectExceptionMessage($message); 20 } 21 } 22 } 23} 24 25/** 26 * Helper class to provide basic functionality for tests 27 * 28 * @uses PHPUnit_Framework_TestCase and thus PHPUnit 5.7+ is required 29 */ 30abstract class DokuWikiTest extends PHPUnit_Framework_TestCase { 31 32 /** 33 * tests can override this 34 * 35 * @var array plugins to enable for test class 36 */ 37 protected $pluginsEnabled = array(); 38 39 /** 40 * tests can override this 41 * 42 * @var array plugins to disable for test class 43 */ 44 protected $pluginsDisabled = array(); 45 46 /** 47 * Setup the data directory 48 * 49 * This is ran before each test class 50 */ 51 public static function setUpBeforeClass() { 52 // just to be safe not to delete something undefined later 53 if(!defined('TMP_DIR')) die('no temporary directory'); 54 if(!defined('DOKU_TMP_DATA')) die('no temporary data directory'); 55 56 self::setupDataDir(); 57 self::setupConfDir(); 58 } 59 60 /** 61 * Reset the DokuWiki environment before each test run. Makes sure loaded config, 62 * language and plugins are correct. 63 * 64 * @throws Exception if plugin actions fail 65 * @return void 66 */ 67 public function setUp() { 68 69 // reload config 70 global $conf, $config_cascade; 71 $conf = array(); 72 foreach (array('default','local','protected') as $config_group) { 73 if (empty($config_cascade['main'][$config_group])) continue; 74 foreach ($config_cascade['main'][$config_group] as $config_file) { 75 if (file_exists($config_file)) { 76 include($config_file); 77 } 78 } 79 } 80 81 // reload license config 82 global $license; 83 $license = array(); 84 85 // load the license file(s) 86 foreach (array('default','local') as $config_group) { 87 if (empty($config_cascade['license'][$config_group])) continue; 88 foreach ($config_cascade['license'][$config_group] as $config_file) { 89 if(file_exists($config_file)){ 90 include($config_file); 91 } 92 } 93 } 94 // reload some settings 95 $conf['gzip_output'] &= (strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip') !== false); 96 97 if($conf['compression'] == 'bz2' && !DOKU_HAS_BZIP) { 98 $conf['compression'] = 'gz'; 99 } 100 if($conf['compression'] == 'gz' && !DOKU_HAS_GZIP) { 101 $conf['compression'] = 0; 102 } 103 // make real paths and check them 104 init_paths(); 105 init_files(); 106 107 // reset loaded plugins 108 global $plugin_controller_class, $plugin_controller; 109 /** @var Doku_Plugin_Controller $plugin_controller */ 110 $plugin_controller = new $plugin_controller_class(); 111 112 // disable all non-default plugins 113 global $default_plugins; 114 foreach ($plugin_controller->getList() as $plugin) { 115 if (!in_array($plugin, $default_plugins)) { 116 if (!$plugin_controller->disable($plugin)) { 117 throw new Exception('Could not disable plugin "'.$plugin.'"!'); 118 } 119 } 120 } 121 122 // disable and enable configured plugins 123 foreach ($this->pluginsDisabled as $plugin) { 124 if (!$plugin_controller->disable($plugin)) { 125 throw new Exception('Could not disable plugin "'.$plugin.'"!'); 126 } 127 } 128 foreach ($this->pluginsEnabled as $plugin) { 129 /* enable() returns false but works... 130 if (!$plugin_controller->enable($plugin)) { 131 throw new Exception('Could not enable plugin "'.$plugin.'"!'); 132 } 133 */ 134 $plugin_controller->enable($plugin); 135 } 136 137 // reset event handler 138 global $EVENT_HANDLER; 139 $EVENT_HANDLER = new EventHandler(); 140 141 // reload language 142 $local = $conf['lang']; 143 trigger_event('INIT_LANG_LOAD', $local, 'init_lang', true); 144 145 global $INPUT; 146 $INPUT = new \dokuwiki\Input\Input(); 147 } 148 149 /** 150 * Reinitialize the data directory for this class run 151 */ 152 public static function setupDataDir() { 153 // remove any leftovers from the last run 154 if(is_dir(DOKU_TMP_DATA)) { 155 // clear indexer data and cache 156 idx_get_indexer()->clear(); 157 TestUtils::rdelete(DOKU_TMP_DATA); 158 } 159 160 // populate default dirs 161 TestUtils::rcopy(TMP_DIR, __DIR__ . '/../data/'); 162 } 163 164 /** 165 * Reinitialize the conf directory for this class run 166 */ 167 public static function setupConfDir() { 168 $defaults = [ 169 'acronyms.conf', 170 'dokuwiki.php', 171 'entities.conf', 172 'interwiki.conf', 173 'license.php', 174 'manifest.json', 175 'mediameta.php', 176 'mime.conf', 177 'plugins.php', 178 'plugins.required.php', 179 'scheme.conf', 180 'smileys.conf', 181 'wordblock.conf' 182 ]; 183 184 // clear any leftovers 185 if(is_dir(DOKU_CONF)) { 186 TestUtils::rdelete(DOKU_CONF); 187 } 188 mkdir(DOKU_CONF); 189 190 // copy defaults 191 foreach($defaults as $file) { 192 copy(DOKU_INC . '/conf/' . $file, DOKU_CONF . $file); 193 } 194 195 // copy test files 196 TestUtils::rcopy(TMP_DIR, __DIR__ . '/../conf'); 197 } 198 199 /** 200 * Waits until a new second has passed 201 * 202 * The very first call will return immeadiately, proceeding calls will return 203 * only after at least 1 second after the last call has passed. 204 * 205 * When passing $init=true it will not return immeadiately but use the current 206 * second as initialization. It might still return faster than a second. 207 * 208 * @param bool $init wait from now on, not from last time 209 * @return int new timestamp 210 */ 211 protected function waitForTick($init = false) { 212 static $last = 0; 213 if($init) $last = time(); 214 while($last === $now = time()) { 215 usleep(100000); //recheck in a 10th of a second 216 } 217 $last = $now; 218 return $now; 219 } 220 221 /** 222 * Allow for testing inaccessible methods (private or protected) 223 * 224 * This makes it easier to test protected methods without needing to create intermediate 225 * classes inheriting and changing the access. 226 * 227 * @link https://stackoverflow.com/a/8702347/172068 228 * @param object $obj Object in which to call the method 229 * @param string $func The method to call 230 * @param array $args The arguments to call the method with 231 * @return mixed 232 * @throws ReflectionException when the given obj/func does not exist 233 */ 234 protected static function callInaccessibleMethod($obj, $func, array $args) { 235 $class = new \ReflectionClass($obj); 236 $method = $class->getMethod($func); 237 $method->setAccessible(true); 238 return $method->invokeArgs($obj, $args); 239 } 240 241 /** 242 * Allow for reading inaccessible properties (private or protected) 243 * 244 * This makes it easier to check internals of tested objects. This should generally 245 * be avoided. 246 * 247 * @param object $obj Object on which to access the property 248 * @param string $prop name of the property to access 249 * @return mixed 250 * @throws ReflectionException when the given obj/prop does not exist 251 */ 252 protected static function getInaccessibleProperty($obj, $prop) { 253 $class = new \ReflectionClass($obj); 254 $property = $class->getProperty($prop); 255 $property->setAccessible(true); 256 return $property->getValue($obj); 257 } 258 259 /** 260 * Allow for reading inaccessible properties (private or protected) 261 * 262 * This makes it easier to set internals of tested objects. This should generally 263 * be avoided. 264 * 265 * @param object $obj Object on which to access the property 266 * @param string $prop name of the property to access 267 * @param mixed $value new value to set the property to 268 * @return void 269 * @throws ReflectionException when the given obj/prop does not exist 270 */ 271 protected static function setInaccessibleProperty($obj, $prop, $value) { 272 $class = new \ReflectionClass($obj); 273 $property = $class->getProperty($prop); 274 $property->setAccessible(true); 275 $property->setValue($obj, $value); 276 } 277} 278