xref: /dokuwiki/_test/core/DokuWikiTest.php (revision e1d9dcc8b460b6f029ac9c8d5d3b8d23b6e73402)
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