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