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